1 package org.apache.maven.plugin.docck;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.commons.httpclient.Credentials;
23 import org.apache.commons.httpclient.HttpClient;
24 import org.apache.commons.httpclient.HttpException;
25 import org.apache.commons.httpclient.UsernamePasswordCredentials;
26 import org.apache.commons.httpclient.auth.AuthScope;
27 import org.apache.commons.httpclient.methods.HeadMethod;
28 import org.apache.commons.httpclient.params.HttpMethodParams;
29 import org.apache.maven.model.IssueManagement;
30 import org.apache.maven.model.License;
31 import org.apache.maven.model.Organization;
32 import org.apache.maven.model.Prerequisites;
33 import org.apache.maven.model.Scm;
34 import org.apache.maven.plugin.AbstractMojo;
35 import org.apache.maven.plugin.MojoExecutionException;
36 import org.apache.maven.plugin.MojoFailureException;
37 import org.apache.maven.plugin.docck.reports.DocumentationReport;
38 import org.apache.maven.plugin.docck.reports.DocumentationReporter;
39 import org.apache.maven.project.MavenProject;
40 import org.apache.maven.settings.Proxy;
41 import org.apache.maven.settings.Settings;
42 import org.apache.maven.shared.model.fileset.FileSet;
43 import org.apache.maven.shared.model.fileset.util.FileSetManager;
44 import org.codehaus.plexus.util.IOUtil;
45 import org.codehaus.plexus.util.StringUtils;
46
47 import java.io.File;
48 import java.io.FileWriter;
49 import java.io.IOException;
50 import java.net.MalformedURLException;
51 import java.net.URL;
52 import java.util.ArrayList;
53 import java.util.Iterator;
54 import java.util.LinkedHashMap;
55 import java.util.List;
56 import java.util.Map;
57
58
59
60
61
62
63
64 public abstract class AbstractCheckDocumentationMojo
65 extends AbstractMojo
66 {
67 private static final int HTTP_STATUS_200 = 200;
68
69
70
71
72
73
74 private List reactorProjects;
75
76
77
78
79
80
81
82 private File output;
83
84
85
86
87
88
89
90 protected String siteDirectory;
91
92
93
94
95
96
97
98 private boolean offline;
99
100
101
102
103
104
105
106
107 private Settings settings;
108
109 private HttpClient httpClient;
110
111 private FileSetManager fileSetManager = new FileSetManager();
112
113 private List validUrls = new ArrayList();
114
115 protected AbstractCheckDocumentationMojo()
116 {
117 String httpUserAgent = "maven-docck-plugin/1.x" + " (Java " + System.getProperty( "java.version" ) + "; "
118 + System.getProperty( "os.name" ) + " " + System.getProperty( "os.version" ) + ")";
119
120 httpClient = new HttpClient();
121
122 final int connectionTimeout = 5000;
123 httpClient.getHttpConnectionManager().getParams().setConnectionTimeout( connectionTimeout );
124 httpClient.getParams().setParameter( HttpMethodParams.USER_AGENT, httpUserAgent );
125 }
126
127 protected List getReactorProjects()
128 {
129 return reactorProjects;
130 }
131
132 public void execute()
133 throws MojoExecutionException, MojoFailureException
134 {
135 setupProxy();
136
137 if ( output != null )
138 {
139 getLog().info( "Writing documentation check results to: " + output );
140 }
141
142 Map reporters = new LinkedHashMap();
143 boolean hasErrors = false;
144
145 for ( Iterator it = reactorProjects.iterator(); it.hasNext(); )
146 {
147 MavenProject project = (MavenProject) it.next();
148
149 if ( approveProjectPackaging( project.getPackaging() ) )
150 {
151 getLog().info( "Checking project: " + project.getName() );
152
153 DocumentationReporter reporter = new DocumentationReporter();
154
155 checkProject( project, reporter );
156
157 if ( !hasErrors && reporter.hasErrors() )
158 {
159 hasErrors = true;
160 }
161
162 reporters.put( project, reporter );
163 }
164 else
165 {
166 getLog().info( "Skipping unsupported project: " + project.getName() );
167 }
168 }
169
170 String messages;
171
172 messages = buildErrorMessages( reporters );
173
174 if ( !hasErrors )
175 {
176 messages += "No documentation errors were found.";
177 }
178
179 try
180 {
181 writeMessages( messages, hasErrors );
182 }
183 catch ( IOException e )
184 {
185 throw new MojoExecutionException( "Error writing results to output file: " + output );
186 }
187
188 if ( hasErrors )
189 {
190 String logLocation;
191 if ( output == null )
192 {
193 logLocation = "Please see the console output above for more information.";
194 }
195 else
196 {
197 logLocation = "Please see \'" + output + "\' for more information.";
198 }
199
200 throw new MojoFailureException( "Documentation problems were found. " + logLocation );
201 }
202 }
203
204
205
206
207 private void setupProxy()
208 {
209 Proxy settingsProxy = settings.getActiveProxy();
210
211 if ( settingsProxy != null )
212 {
213 String proxyUsername = settingsProxy.getUsername();
214
215 String proxyPassword = settingsProxy.getPassword();
216
217 String proxyHost = settingsProxy.getHost();
218
219 int proxyPort = settingsProxy.getPort();
220
221 if ( StringUtils.isNotEmpty( proxyHost ) )
222 {
223 httpClient.getHostConfiguration().setProxy( proxyHost, proxyPort );
224
225 getLog().info( "Using proxy [" + proxyHost + "] at port [" + proxyPort + "]." );
226
227 if ( StringUtils.isNotEmpty( proxyUsername ) )
228 {
229 getLog().info( "Using proxy user [" + proxyUsername + "]." );
230
231 Credentials creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword );
232
233 httpClient.getState().setProxyCredentials( new AuthScope( proxyHost, proxyPort ), creds );
234 httpClient.getParams().setAuthenticationPreemptive( true );
235 }
236 }
237 }
238 }
239
240 private String buildErrorMessages( Map reporters )
241 {
242 String messages = "";
243 StringBuffer buffer = new StringBuffer();
244
245 for ( Iterator it = reporters.entrySet().iterator(); it.hasNext(); )
246 {
247 Map.Entry entry = (Map.Entry) it.next();
248
249 MavenProject project = (MavenProject) entry.getKey();
250 DocumentationReporter reporter = (DocumentationReporter) entry.getValue();
251
252 if ( !reporter.getMessages().isEmpty() )
253 {
254 buffer.append( "\no " ).append( project.getName() );
255 buffer.append( " (" );
256 final int numberOfErrors = reporter.getMessagesByType( DocumentationReport.TYPE_ERROR ).size();
257 buffer.append( numberOfErrors ).append( " error" ).append( numberOfErrors == 1 ? "" : "s" );
258 buffer.append( ", " );
259 final int numberOfWarnings = reporter.getMessagesByType( DocumentationReport.TYPE_WARN ).size();
260 buffer.append( numberOfWarnings ).append( " warning" ).append( numberOfWarnings == 1 ? "" : "s" );
261 buffer.append( ")" );
262 buffer.append( "\n" );
263
264 for ( Iterator errorIterator = reporter.getMessages().iterator(); errorIterator.hasNext(); )
265 {
266 String error = (String) errorIterator.next();
267
268 buffer.append( " " ).append( error ).append( "\n" );
269 }
270 }
271 }
272
273 if ( buffer.length() > 0 )
274 {
275 messages = "The following documentation problems were found:\n" + buffer.toString();
276 }
277
278 return messages;
279 }
280
281 protected abstract boolean approveProjectPackaging( String packaging );
282
283
284
285
286
287
288
289
290 private void writeMessages( String messages, boolean hasErrors )
291 throws IOException
292 {
293 if ( output != null )
294 {
295 FileWriter writer = null;
296
297 try
298 {
299 writer = new FileWriter( output );
300 writer.write( messages );
301 writer.flush();
302 }
303 finally
304 {
305 IOUtil.close( writer );
306 }
307 }
308 else
309 {
310 if ( hasErrors )
311 {
312 getLog().error( messages );
313 }
314 else
315 {
316 getLog().info( messages );
317 }
318 }
319 }
320
321 private void checkProject( MavenProject project, DocumentationReporter reporter )
322 {
323 checkPomRequirements( project, reporter );
324
325 checkPackagingSpecificDocumentation( project, reporter );
326 }
327
328 private void checkPomRequirements( MavenProject project, DocumentationReporter reporter )
329 {
330 checkProjectLicenses( project, reporter );
331
332 if ( StringUtils.isEmpty( project.getName() ) )
333 {
334 reporter.error( "pom.xml is missing the <name> tag." );
335 }
336
337 if ( StringUtils.isEmpty( project.getDescription() ) )
338 {
339 reporter.error( "pom.xml is missing the <description> tag." );
340 }
341
342 if ( StringUtils.isEmpty( project.getUrl() ) )
343 {
344 reporter.error( "pom.xml is missing the <url> tag." );
345 }
346 else
347 {
348 checkURL( project.getUrl(), "project site", reporter );
349 }
350
351 if ( project.getIssueManagement() == null )
352 {
353 reporter.error( "pom.xml is missing the <issueManagement> tag." );
354 }
355 else
356 {
357 IssueManagement issueMngt = project.getIssueManagement();
358 if ( StringUtils.isEmpty( issueMngt.getUrl() ) )
359 {
360 reporter.error( "pom.xml is missing the <url> tag in <issueManagement>." );
361 }
362 else
363 {
364 checkURL( issueMngt.getUrl(), "Issue Management", reporter );
365 }
366 }
367
368 if ( project.getPrerequisites() == null )
369 {
370 reporter.error( "pom.xml is missing the <prerequisites> tag." );
371 }
372 else
373 {
374 Prerequisites prereq = project.getPrerequisites();
375 if ( StringUtils.isEmpty( prereq.getMaven() ) )
376 {
377 reporter.error( "pom.xml is missing the <prerequisites>/<maven> tag." );
378 }
379 }
380
381 if ( StringUtils.isEmpty( project.getInceptionYear() ) )
382 {
383 reporter.error( "pom.xml is missing the <inceptionYear> tag." );
384 }
385
386 if ( project.getMailingLists().size() == 0 )
387 {
388 reporter.warn( "pom.xml has no <mailingLists>/<mailingList> specified." );
389 }
390
391 if ( project.getScm() == null )
392 {
393 reporter.warn( "pom.xml is missing the <scm> tag." );
394 }
395 else
396 {
397 Scm scm = project.getScm();
398 if ( StringUtils.isEmpty( scm.getConnection() ) && StringUtils.isEmpty( scm.getDeveloperConnection() )
399 && StringUtils.isEmpty( scm.getUrl() ) )
400 {
401 reporter.warn( "pom.xml is missing the child tags under the <scm> tag." );
402 }
403 else if ( scm.getUrl() != null )
404 {
405 checkURL( scm.getUrl(), "scm", reporter );
406 }
407 }
408
409 if ( project.getOrganization() == null )
410 {
411 reporter.error( "pom.xml is missing the <organization> tag." );
412 }
413 else
414 {
415 Organization org = project.getOrganization();
416 if ( StringUtils.isEmpty( org.getName() ) )
417 {
418 reporter.error( "pom.xml is missing the <organization>/<name> tag." );
419 }
420 else if ( org.getUrl() != null )
421 {
422 checkURL( org.getUrl(), org.getName() + " site", reporter );
423 }
424 }
425 }
426
427 private void checkProjectLicenses( MavenProject project, DocumentationReporter reporter )
428 {
429 List licenses = project.getLicenses();
430
431 if ( licenses == null || licenses.isEmpty() )
432 {
433 reporter.error( "pom.xml has no <licenses>/<license> specified." );
434 }
435 else
436 {
437 for ( Iterator it = licenses.iterator(); it.hasNext(); )
438 {
439 License license = (License) it.next();
440
441 if ( StringUtils.isEmpty( license.getName() ) )
442 {
443 reporter.error( "pom.xml is missing the <licenses>/<license>/<name> tag." );
444 }
445 else
446 {
447 String url = license.getUrl();
448 if ( StringUtils.isEmpty( url ) )
449 {
450 reporter.error( "pom.xml is missing the <licenses>/<license>/<url> tag for the license \'"
451 + license.getName() + "\'." );
452 }
453 else
454 {
455 checkURL( url, "license \'" + license.getName() + "\'", reporter );
456 }
457 }
458 }
459 }
460 }
461
462 private String getURLProtocol( String url )
463 throws MalformedURLException
464 {
465 String protocol;
466
467 URL licenseUrl = new URL( url );
468 protocol = licenseUrl.getProtocol();
469
470 if ( protocol != null )
471 {
472 protocol = protocol.toLowerCase();
473 }
474
475 return protocol;
476 }
477
478 private void checkURL( String url, String description, DocumentationReporter reporter )
479 {
480 try
481 {
482 String protocol = getURLProtocol( url );
483
484 if ( protocol.startsWith( "http" ) )
485 {
486 if ( offline )
487 {
488 reporter.warn( "Cannot verify " + description + " in offline mode with URL: \'" + url + "\'." );
489 }
490 else if ( !validUrls.contains( url ) )
491 {
492 HeadMethod headMethod = new HeadMethod( url );
493 headMethod.setFollowRedirects( true );
494 headMethod.setDoAuthentication( false );
495
496 try
497 {
498 getLog().debug( "Verifying http url: " + url );
499 if ( httpClient.executeMethod( headMethod ) != HTTP_STATUS_200 )
500 {
501 reporter.error( "Cannot reach " + description + " with URL: \'" + url + "\'." );
502 }
503 else
504 {
505 validUrls.add( url );
506 }
507 }
508 catch ( HttpException e )
509 {
510 reporter.error( "Cannot reach " + description + " with URL: \'" + url + "\'.\nError: "
511 + e.getMessage() );
512 }
513 catch ( IOException e )
514 {
515 reporter.error( "Cannot reach " + description + " with URL: \'" + url + "\'.\nError: "
516 + e.getMessage() );
517 }
518 finally
519 {
520 headMethod.releaseConnection();
521 }
522 }
523 }
524 else
525 {
526 reporter.warn( "Non-HTTP " + description + " URL not verified." );
527 }
528 }
529 catch ( MalformedURLException e )
530 {
531 reporter.warn( "The " + description + " appears to have an invalid URL \'" + url + "\'."
532 + " Message: \'" + e.getMessage() + "\'. Trying to access it as a file instead." );
533
534 checkFile( url, description, reporter );
535 }
536 }
537
538 private void checkFile( String url, String description, DocumentationReporter reporter )
539 {
540 File licenseFile = new File( url );
541 if ( !licenseFile.exists() )
542 {
543 reporter.error( "The " + description + " in file \'" + licenseFile.getPath() + "\' does not exist." );
544 }
545 }
546
547 protected abstract void checkPackagingSpecificDocumentation( MavenProject project, DocumentationReporter reporter );
548
549 protected boolean findFiles( File siteDirectory, String pattern )
550 {
551 FileSet fs = new FileSet();
552 fs.setDirectory( siteDirectory.getAbsolutePath() );
553 fs.setFollowSymlinks( false );
554
555 fs.addInclude( "apt/" + pattern + ".apt" );
556 fs.addInclude( "apt/" + pattern + ".apt.vm" );
557 fs.addInclude( "xdoc/" + pattern + ".xml" );
558 fs.addInclude( "xdoc/" + pattern + ".xml.vm" );
559 fs.addInclude( "fml/" + pattern + ".fml" );
560 fs.addInclude( "fml/" + pattern + ".fml.vm" );
561 fs.addInclude( "resources/" + pattern + ".html" );
562 fs.addInclude( "resources/" + pattern + ".html.vm" );
563
564 String[] includedFiles = fileSetManager.getIncludedFiles( fs );
565
566 return includedFiles != null && includedFiles.length > 0;
567 }
568 }