View Javadoc

1   package org.apache.maven.doxia.linkcheck.validation;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.maven.doxia.linkcheck.model.LinkcheckFileResult;
25  import org.codehaus.plexus.util.IOUtil;
26  import org.codehaus.plexus.util.SelectorUtils;
27  import org.codehaus.plexus.util.StringUtils;
28  
29  import java.io.File;
30  import java.io.FileInputStream;
31  import java.io.FileOutputStream;
32  import java.io.IOException;
33  import java.io.InvalidClassException;
34  import java.io.ObjectInputStream;
35  import java.io.ObjectOutputStream;
36  import java.io.Serializable;
37  
38  import java.net.URI;
39  import java.net.URISyntaxException;
40  
41  import java.util.HashMap;
42  import java.util.Iterator;
43  import java.util.LinkedList;
44  import java.util.List;
45  import java.util.Map;
46  
47  /**
48   * A LinkValidator manager which manages validators with a cache.
49   *
50   * @author <a href="mailto:bwalding@apache.org">Ben Walding</a>
51   * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a>
52   * @author <a href="mailto:aheritier@apache.org">Arnaud Heritier</a>
53   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
54   * @version $Id: LinkValidatorManager.java 1004651 2010-10-05 14:09:10Z ltheussl $
55   */
56  public class LinkValidatorManager
57      implements Serializable
58  {
59      /** serialVersionUID. */
60      private static final long serialVersionUID = 2467928182206500945L;
61  
62      /** Log for debug output. */
63      private static final Log LOG = LogFactory.getLog( LinkValidatorManager.class );
64  
65      /** validators. */
66      private List validators = new LinkedList();
67  
68      /** excludes. */
69      private String[] excludedLinks = new String[0];
70  
71      /** cache. */
72      private Map cache = new HashMap();
73  
74      /**
75       * Returns the list of validators.
76       *
77       * @return List
78       */
79      public List getValidators()
80      {
81          return this.validators;
82      }
83  
84      /**
85       * Returns the excludedLinks.
86       * Could contains a link, i.e. <code>http:&#47;&#47;maven.apache.org/</code>,
87       * or pattern links i.e. <code>http:&#47;&#47;maven.apache.org&#47;**&#47;*.html</code>
88       *
89       * @return String[]
90       */
91      public String[] getExcludedLinks()
92      {
93          return this.excludedLinks;
94      }
95  
96      /**
97       * Sets the excludedLinks.
98       * Could contains a link, i.e. <code>http:&#47;&#47;maven.apache.org/</code>,
99       * or pattern links i.e. <code>http:&#47;&#47;maven.apache.org&#47;**&#47;*.html</code>
100      *
101      * @param excl The excludes to set.
102      */
103     public void setExcludedLinks( String[] excl )
104     {
105         this.excludedLinks = excl;
106     }
107 
108     /**
109      * Adds a LinkValidator to this manager.
110      *
111      * @param lv The LinkValidator to add.
112      */
113     public void addLinkValidator( LinkValidator lv )
114     {
115         this.validators.add( lv );
116     }
117 
118     /**
119      * Validates the links of the given LinkValidationItem.
120      *
121      * @param lvi The LinkValidationItem to validate.
122      * @return A LinkValidationResult.
123      */
124     public LinkValidationResult validateLink( LinkValidationItem lvi )
125     {
126         LinkValidationResult cachedResult = getCachedResult( lvi );
127 
128         if ( cachedResult != null )
129         {
130             return cachedResult;
131         }
132 
133         for ( int i = 0; i < this.excludedLinks.length; i++ )
134         {
135             if ( this.excludedLinks[i] != null && matchPattern( lvi.getLink(), this.excludedLinks[i] ) )
136             {
137                 if ( LOG.isDebugEnabled() )
138                 {
139                     LOG.debug( "Excluded " + lvi.getLink() );
140                 }
141 
142                 return new LinkValidationResult( LinkcheckFileResult.VALID_LEVEL, false, "" );
143             }
144         }
145 
146         Iterator iter = this.validators.iterator();
147 
148         LinkValidator lv;
149 
150         Object resourceKey;
151 
152         LinkValidationResult lvr;
153 
154         while ( iter.hasNext() )
155         {
156             lv = (LinkValidator) iter.next();
157 
158             resourceKey = lv.getResourceKey( lvi );
159 
160             if ( resourceKey != null )
161             {
162                 if ( LOG.isDebugEnabled() )
163                 {
164                     LOG.debug( lv.getClass().getName() + " - Checking link " + lvi.getLink() );
165                 }
166 
167                 lvr = lv.validateLink( lvi );
168 
169                 if ( lvr.getStatus() == LinkValidationResult.NOTMINE )
170                 {
171                     continue;
172                 }
173 
174                 setCachedResult( resourceKey, lvr );
175 
176                 return lvr;
177             }
178         }
179 
180         lv = null;
181 
182         resourceKey = null;
183 
184         lvr = null;
185 
186         if ( LOG.isErrorEnabled() )
187         {
188             LOG.error( "Unable to validate link : " + lvi.getLink() );
189         }
190 
191         return new LinkValidationResult( LinkcheckFileResult.UNKNOWN_LEVEL, false,
192                                          "No validator found for this link" );
193     }
194 
195     /**
196      * Loads a cache file.
197      *
198      * @param cacheFile The cache file.
199      * May be null, in which case the request is ignored.
200      * @throws IOException if any
201      */
202     public void loadCache( File cacheFile )
203         throws IOException
204     {
205         if ( cacheFile == null )
206         {
207             LOG.debug( "No cache file specified! Ignoring request to load." );
208             return;
209         }
210 
211         if ( !cacheFile.exists() )
212         {
213             LOG.debug( "Specified cache file does not exist! Ignoring request to load." );
214             return;
215         }
216 
217         if ( cacheFile.isDirectory() )
218         {
219             LOG.debug( "Cache file is a directory! Ignoring request to load." );
220             return;
221         }
222 
223         ObjectInputStream is = null;
224         try
225         {
226             is = new ObjectInputStream( new FileInputStream( cacheFile ) );
227 
228             this.cache = (Map) is.readObject();
229 
230             if ( LOG.isDebugEnabled() )
231             {
232                 LOG.debug( "Cache file loaded: " + cacheFile.getAbsolutePath() );
233             }
234         }
235         catch ( InvalidClassException e )
236         {
237             LOG.warn( "Your cache is incompatible with this version of linkcheck. It will be recreated." );
238         }
239         catch ( ClassNotFoundException e )
240         {
241             if ( LOG.isErrorEnabled() )
242             {
243                 LOG.error( "Unable to load the cache: " + cacheFile.getAbsolutePath(), e );
244             }
245         }
246         finally
247         {
248             IOUtil.close( is );
249         }
250     }
251 
252     /**
253      * Saves a cache file.
254      *
255      * @param cacheFile The name of the cache file.
256      * May be null, in which case the request is ignored.
257      * @throws IOException if any
258      */
259     public void saveCache( File cacheFile )
260         throws IOException
261     {
262         if ( cacheFile == null )
263         {
264             LOG.warn( "No cache file specified! Ignoring request to store results." );
265             return;
266         }
267 
268         if ( cacheFile.isDirectory() )
269         {
270             LOG.debug( "Cache file is a directory! Ignoring request to load." );
271             return;
272         }
273 
274         // Remove non-persistent items from cache
275         Map persistentCache = new HashMap();
276 
277         Iterator iter = this.cache.keySet().iterator();
278 
279         Object resourceKey;
280 
281         while ( iter.hasNext() )
282         {
283             resourceKey = iter.next();
284 
285             if ( ( (LinkValidationResult) this.cache.get( resourceKey ) ).isPersistent() )
286             {
287                 persistentCache.put( resourceKey, this.cache.get( resourceKey ) );
288 
289                 if ( LOG.isDebugEnabled() )
290                 {
291                     LOG.debug( "[" + resourceKey + "] with result [" + this.cache.get( resourceKey )
292                         + "] is stored in the cache." );
293                 }
294             }
295         }
296 
297         File dir = cacheFile.getParentFile();
298         if ( dir != null )
299         {
300             dir.mkdirs();
301         }
302 
303         ObjectOutputStream os = null;
304         try
305         {
306             os = new ObjectOutputStream( new FileOutputStream( cacheFile ) );
307 
308             os.writeObject( persistentCache );
309         }
310         finally
311         {
312             persistentCache = null;
313 
314             iter = null;
315 
316             resourceKey = null;
317 
318             cacheFile = null;
319 
320             dir = null;
321 
322             IOUtil.close( os );
323         }
324     }
325 
326     /**
327      * Returns a LinkValidationResult for the given LinkValidationItem
328      * if it has been cached from a previous run, returns null otherwise.
329      *
330      * @param lvi The LinkValidationItem.
331      * @return LinkValidationResult
332      */
333     public LinkValidationResult getCachedResult( LinkValidationItem lvi )
334     {
335         Iterator iter = getValidators().iterator();
336 
337         LinkValidator lv;
338 
339         Object resourceKey;
340 
341         while ( iter.hasNext() )
342         {
343             lv = (LinkValidator) iter.next();
344 
345             resourceKey = lv.getResourceKey( lvi );
346 
347             if ( resourceKey != null && this.cache.containsKey( resourceKey ) )
348             {
349                 if ( LOG.isDebugEnabled() )
350                 {
351                     LOG.debug( "The cache returns for [" + resourceKey + "] the result ["
352                         + this.cache.get( resourceKey ) + "]." );
353                 }
354 
355                 return (LinkValidationResult) this.cache.get( resourceKey );
356             }
357         }
358 
359         lv = null;
360 
361         resourceKey = null;
362 
363         return null;
364     }
365 
366     /**
367      * Puts the given LinkValidationResult into the cache.
368      *
369      * @param resourceKey The key to retrieve the result.
370      * @param lvr the LinkValidationResult to cache.
371      */
372     public void setCachedResult( Object resourceKey, LinkValidationResult lvr )
373     {
374         this.cache.put( resourceKey, lvr );
375     }
376 
377     /**
378      * @param link not null
379      * @param pattern not null
380      * @return true if pattern match
381      */
382     protected static boolean matchPattern( String link, String pattern )
383     {
384         if ( StringUtils.isEmpty( pattern ) )
385         {
386             return StringUtils.isEmpty( link );
387         }
388 
389         if ( pattern.indexOf( '*' ) == -1 )
390         {
391             if ( pattern.endsWith( "/" ) )
392             {
393                 return link.indexOf( pattern.substring( 0, pattern.lastIndexOf( '/' ) ) ) != -1;
394             }
395 
396             return link.indexOf( pattern ) != -1;
397         }
398 
399         try
400         {
401             URI uri = new URI( link );
402 
403             if ( uri.getScheme() != null && !pattern.startsWith( uri.getScheme() ) )
404             {
405                 return false;
406             }
407         }
408         catch ( URISyntaxException ex )
409         {
410             LOG.debug( "Trying to check link to illegal URI: " + link, ex );
411         }
412 
413         if ( pattern.matches( "\\*+/?.*" ) && !link.startsWith( "/" ) && !link.startsWith( "./" ) )
414         {
415             link = "./" + link;
416         }
417         String diff = StringUtils.difference( link, pattern );
418         if ( diff.startsWith( "/" ) )
419         {
420             return SelectorUtils.match( pattern, link + "/" );
421         }
422 
423         return SelectorUtils.match( pattern, link );
424     }
425 }