blob: 12ea2f1b7c67f5003144580030c20762a8a47db5 [file] [log] [blame]
package org.apache.maven.dist.tools;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import org.apache.maven.doxia.markup.HtmlMarkup;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.SinkEventAttributeSet;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.reporting.MavenReportException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
/**
*
* Check presence of source-release.zip in dist repo and central repo
*
* @author skygo
*/
@Mojo( name = "check-source-release", requiresProject = false )
public class DistCheckSourceReleaseMojo
extends AbstractDistCheckMojo
{
private static final String DIST_AREA = "http://www.apache.org/dist/maven/";
//private static final String DIST_SVNPUBSUB = "https://dist.apache.org/repos/dist/release/maven/";
@Override
boolean useDetailed()
{
return false;
}
/**
* Ignore dist failure for <code>artifactId</code> or <code>artifactId:version</code>
*/
@Parameter
protected List<String> ignoreDistFailures;
@Override
public String getOutputName()
{
return "dist-tool-checksourcerelease";
}
@Override
public String getName( Locale locale )
{
return "Disttool> Source Release";
}
@Override
public String getDescription( Locale locale )
{
return "Verification of source release";
}
private static class DistCheckSourceRelease
extends AbstractCheckResult
{
private List<String> central;
private List<String> dist;
private List<String> distOlder;
public DistCheckSourceRelease( ConfigurationLineInfo r, String version )
{
super( r, version );
}
private void setMissingDistSourceRelease( List<String> checkRepos )
{
dist = checkRepos;
}
private void setMissingCentralSourceRelease( List<String> checkRepos )
{
central = checkRepos;
}
private void setDistOlderSourceRelease( List<String> checkRepos )
{
distOlder = checkRepos;
}
}
private final List<DistCheckSourceRelease> results = new LinkedList<>();
private static class DirectoryStatistics
{
final String directory;
int artifactsCount = 0;
int centralMissing = 0;
int distError = 0;
int distMissing = 0;
int distOlder = 0;
public DirectoryStatistics( String directory )
{
this.directory = directory;
}
public boolean contains( DistCheckSourceRelease csr )
{
return csr.getConfigurationLine().getDirectory().equals( directory );
}
public void addArtifact( DistCheckSourceRelease result )
{
artifactsCount++;
if ( !result.central.isEmpty() )
{
centralMissing++;
}
if ( !result.dist.isEmpty() || !result.distOlder.isEmpty() )
{
distError++;
}
if ( !result.dist.isEmpty() )
{
distMissing++;
}
if ( !result.distOlder.isEmpty() )
{
distOlder++;
}
}
}
private void reportLine( Sink sink, DistCheckSourceRelease csr )
{
ConfigurationLineInfo cli = csr.getConfigurationLine();
sink.tableRow();
sink.tableCell();
sink.rawText( csr.getConfigurationLine().getArtifactId() );
sink.tableCell_();
// LATEST column
sink.tableCell();
sink.link( cli.getMetadataFileURL( repoBaseUrl ) );
sink.rawText( csr.getVersion() );
sink.link_();
sink.tableCell_();
// DATE column
sink.tableCell();
sink.rawText( csr.getConfigurationLine().getReleaseFromMetadata() );
sink.tableCell_();
// central column
sink.tableCell();
sink.link( cli.getBaseURL( repoBaseUrl, "" ) );
sink.text( "artifact" );
sink.link_();
sink.text( "/" );
sink.link( cli.getVersionnedFolderURL( repoBaseUrl, csr.getVersion() ) );
sink.text( csr.getVersion() );
sink.link_();
sink.text( "/" );
if ( csr.central.isEmpty() )
{
iconSuccess( sink );
}
else
{
iconWarning( sink );
}
for ( String missing : csr.central )
{
sink.lineBreak();
iconError( sink );
sink.rawText( missing );
}
sink.tableCell_();
// dist column
sink.tableCell();
String directory = cli.getDirectory() + ( cli.isSrcBin() ? ( "/" + csr.getVersion() + "/source" ) : "" );
sink.link( DIST_AREA + directory );
sink.text( directory );
sink.link_();
sink.text( "source-release" );
if ( csr.dist.isEmpty() && csr.distOlder.isEmpty() )
{
iconSuccess( sink );
}
else
{
iconWarning( sink );
}
StringBuilder cliMissing = new StringBuilder();
for ( String missing : csr.dist )
{
sink.lineBreak();
iconError( sink );
sink.rawText( missing );
if ( !csr.central.contains( missing ) )
{
// if the release distribution is in central repository, we can get it from there...
cliMissing.append( "\nwget " ).append( cli.getVersionnedFolderURL( repoBaseUrl, csr.getVersion() ) ).
append( "/" ).append( missing );
cliMissing.append( "\nsvn add " ).append( missing );
}
}
if ( !cliMissing.toString().isEmpty() )
{
sink.lineBreak();
SinkEventAttributeSet atts = new SinkEventAttributeSet();
sink.unknown( "pre", new Object[]
{
new Integer( HtmlMarkup.TAG_TYPE_START )
}, atts );
sink.text( cliMissing.toString() );
sink.unknown( "pre", new Object[]
{
new Integer( HtmlMarkup.TAG_TYPE_END )
}, null );
}
StringBuilder cliOlder = new StringBuilder();
for ( String missing : csr.distOlder )
{
sink.lineBreak();
iconRemove( sink );
sink.rawText( missing );
cliOlder.append( "\nsvn rm " ).append( missing );
}
if ( !cliOlder.toString().isEmpty() )
{
sink.lineBreak();
SinkEventAttributeSet atts = new SinkEventAttributeSet();
sink.unknown( "pre", new Object[]
{
new Integer( HtmlMarkup.TAG_TYPE_START )
}, atts );
sink.text( cliOlder.toString() );
sink.unknown( "pre", new Object[]
{
new Integer( HtmlMarkup.TAG_TYPE_END )
}, null );
}
sink.tableCell_();
sink.tableRow_();
}
@Override
protected void executeReport( Locale locale )
throws MavenReportException
{
if ( !outputDirectory.exists() )
{
outputDirectory.mkdirs();
}
try
{
this.execute();
}
catch ( MojoExecutionException ex )
{
throw new MavenReportException( ex.getMessage(), ex );
}
DirectoryStatistics stats = new DirectoryStatistics( "" ); // global stats
List<DirectoryStatistics> statistics = new ArrayList<>();
DirectoryStatistics current = null;
for ( DistCheckSourceRelease csr : results )
{
if ( ( current == null ) || !current.contains( csr ) )
{
current = new DirectoryStatistics( csr.getConfigurationLine().getDirectory() );
statistics.add( current );
}
current.addArtifact( csr );
stats.addArtifact( csr );
}
Sink sink = getSink();
sink.head();
sink.title();
sink.text( "Check source release" );
sink.title_();
sink.head_();
sink.body();
sink.section1();
sink.paragraph();
sink.text( "Check Source Release"
+ " (= artifactId + version + '-source-release.zip[.asc|.md5]') availability in:" );
sink.paragraph_();
sink.list();
sink.listItem();
sink.link( repoBaseUrl );
sink.text( "central" );
sink.link_();
sink.listItem_();
sink.listItem();
sink.link( DIST_AREA );
sink.text( "Apache distribution area" );
sink.link_();
sink.listItem_();
sink.list_();
sink.paragraph();
sink.text( "Older artifacts exploration is Work In Progress..." );
sink.paragraph_();
sink.section1_();
sink.table();
sink.tableRow();
sink.tableHeaderCell();
sink.rawText( "groupId/artifactId: " + String.valueOf( stats.artifactsCount ) );
sink.tableHeaderCell_();
sink.tableHeaderCell();
sink.rawText( "LATEST" );
sink.tableHeaderCell_();
sink.tableHeaderCell();
sink.rawText( "DATE" );
sink.tableHeaderCell_();
reportStatisticsHeader( stats, sink );
sink.tableRow_();
Iterator<DirectoryStatistics> dirs = statistics.iterator();
current = null;
for ( DistCheckSourceRelease csr : results )
{
if ( ( current == null ) || !current.contains( csr ) )
{
current = dirs.next();
sink.tableRow();
sink.tableHeaderCell();
// shorten groupid
sink.rawText( csr.getConfigurationLine().getGroupId().replaceAll( "org.apache.maven", "o.a.m" ) + ": "
+ String.valueOf( current.artifactsCount ) );
sink.tableHeaderCell_();
sink.tableHeaderCell();
sink.rawText( " " );
sink.tableHeaderCell_();
sink.tableHeaderCell();
sink.rawText( " " );
sink.tableHeaderCell_();
reportStatisticsHeader( current, sink );
sink.tableRow_();
}
reportLine( sink, csr );
}
sink.table_();
sink.body_();
sink.flush();
sink.close();
}
private void reportStatisticsHeader( DirectoryStatistics current, Sink sink )
{
sink.tableHeaderCell();
sink.rawText( "central: " + String.valueOf( current.artifactsCount - current.centralMissing ) );
iconSuccess( sink );
if ( current.centralMissing > 0 )
{
sink.rawText( "/" + String.valueOf( current.centralMissing ) );
iconWarning( sink );
}
sink.tableHeaderCell_();
sink.tableHeaderCell();
sink.rawText( "dist: " + String.valueOf( current.artifactsCount - current.distError ) );
iconSuccess( sink );
if ( current.distError > 0 )
{
sink.rawText( "/" + String.valueOf( current.distError ) );
iconWarning( sink );
sink.rawText( "= " + String.valueOf( current.distMissing ) );
iconError( sink );
sink.rawText( "/" + String.valueOf( current.distOlder ) );
iconRemove( sink );
}
sink.tableHeaderCell_();
}
/**
* Report a pattern for an artifact source release.
*
* @param artifact artifact name
* @return regex
*/
protected static String getSourceReleasePattern( String artifact )
{
/// not the safest
return "^" + artifact + "-[0-9].*source-release.*$";
}
private Elements selectLinks( String repourl )
throws IOException
{
try
{
Document doc = Jsoup.connect( repourl ).get();
return doc.select( "a[href]" );
}
catch ( IOException ioe )
{
throw new IOException( "IOException while reading " + repourl, ioe );
}
}
private List<String> checkContainsOld( String url, ConfigurationLineInfo cli, String version )
throws IOException
{
Elements links = selectLinks( url );
List<String> retrievedFile = new LinkedList<>();
for ( Element e : links )
{
String art = e.attr( "href" );
if ( art.matches( getSourceReleasePattern( cli.getArtifactId() ) ) )
{
retrievedFile.add( e.attr( "href" ) );
}
}
List<String> expectedFiles = cli.getExpectedFilenames( version, true );
retrievedFile.removeAll( expectedFiles );
if ( !retrievedFile.isEmpty() )
{
// write the following output in red so it's more readable in jenkins console
addErrorLine( cli, version, ignoreDistFailures,
"Older version than " + version + " for " + cli.getArtifactId() + " still available in "
+ url );
for ( String sourceItem : retrievedFile )
{
addErrorLine( cli, version, ignoreDistFailures, " > " + sourceItem + " <" );
}
}
return retrievedFile;
}
/**
* Check that url points to a directory index containing expected release files
* @param url
* @param cli
* @param version
* @return missing files
* @throws IOException
*/
private List<String> checkDirectoryIndex( String url, ConfigurationLineInfo cli, String version, boolean dist )
throws IOException
{
List<String> retrievedFile = new LinkedList<>();
Elements links = selectLinks( url );
for ( Element e : links )
{
retrievedFile.add( e.attr( "href" ) );
}
List<String> missingFiles;
// initialize missing files with expected release file names
missingFiles = cli.getExpectedFilenames( version, dist );
// removed retrieved files
missingFiles.removeAll( retrievedFile );
if ( !missingFiles.isEmpty() )
{
addErrorLine( cli, version, ignoreDistFailures, "Missing file for " + cli.getArtifactId() + " in " + url );
for ( String sourceItem : missingFiles )
{
addErrorLine( cli, version, ignoreDistFailures, " > " + sourceItem + " <" );
}
}
return missingFiles;
}
@Override
void checkArtifact( ConfigurationLineInfo configLine, String version )
throws MojoExecutionException
{
try
{
DistCheckSourceRelease result = new DistCheckSourceRelease( configLine, version );
results.add( result );
// central
String centralUrl = configLine.getVersionnedFolderURL( repoBaseUrl, version );
result.setMissingCentralSourceRelease( checkDirectoryIndex( centralUrl, configLine, version, false ) );
// dist
String distUrl =
DIST_AREA + configLine.getDirectory() + ( configLine.isSrcBin() ? ( "/" + version + "/source" ) : "" );
result.setMissingDistSourceRelease( checkDirectoryIndex( distUrl, configLine, version, true ) );
result.setDistOlderSourceRelease( checkContainsOld( distUrl, configLine, version ) );
}
catch ( IOException ex )
{
throw new MojoExecutionException( ex.getMessage(), ex );
}
}
}