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