/*
* Copyright (c) 2010 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* <http://www.gnu.org/licenses>.
*/
package com.redhat.rcm.version.util;
import static java.io.File.separatorChar;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.maven.mae.project.key.ProjectKey;
import org.apache.maven.model.Model;
import org.apache.maven.model.ModelBase;
import org.apache.maven.model.Profile;
import org.apache.maven.model.io.jdom.MavenJDOMWriter;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.WriterFactory;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.jdom.Attribute;
import org.jdom.Comment;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.filter.ContentFilter;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.Format.TextMode;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.redhat.rcm.version.VManException;
import com.redhat.rcm.version.mgr.session.VersionManagerSession;
public final class PomUtils
{
private static final Logger logger = LoggerFactory.getLogger( PomUtils.class );
private PomUtils()
{
}
public static Model cloneModel( final Model src )
throws VManException
{
try
{
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
new MavenXpp3Writer().write( baos, src );
return new MavenXpp3Reader().read( new ByteArrayInputStream( baos.toByteArray() ) );
}
catch ( final IOException e )
{
throw new VManException( "Failed to clone model %s via serialization/deserialization. Reason: %s", e, src,
e.getMessage() );
}
catch ( final XmlPullParserException e )
{
throw new VManException( "Failed to clone model %s via serialization/deserialization. Reason: %s", e, src,
e.getMessage() );
}
}
public static File writeModifiedPom( final Model model, final File pom, final ProjectKey coord,
final String version, final File basedir, final VersionManagerSession session,
final boolean relocatePom )
{
normalizeModel( model );
final File out = relocatePom ? generateRelocatedPomFile( coord, version, basedir ) : pom;
Writer writer = null;
try
{
final SAXBuilder builder = new SAXBuilder();
Document doc = builder.build( pom );
String encoding = model.getModelEncoding();
if ( encoding == null )
{
encoding = "UTF-8";
}
final Format format = Format.getRawFormat()
.setEncoding( "UTF-8" )
.setTextMode( TextMode.PRESERVE )
.setLineSeparator( System.getProperty( "line.separator" ) )
.setOmitDeclaration( false )
.setOmitEncoding( false )
.setExpandEmptyElements( true );
logger.info( "Writing modified POM:\n\n" + new XMLOutputter( format ).outputString( doc ) );
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
writer = WriterFactory.newWriter( baos, encoding );
new MavenJDOMWriter().write( model, doc, writer, format );
doc = builder.build( new ByteArrayInputStream( baos.toByteArray() ) );
normalizeNamespace( doc );
final Iterator<?> it = doc.getRootElement()
.getContent( new ContentFilter( ContentFilter.COMMENT ) )
.iterator();
while ( it.hasNext() )
{
final Comment c = (Comment) it.next();
if ( c.toString()
.startsWith( "[Comment: <!-- Modified by " + com.redhat.rcm.version.stats.VersionInfo.APP_NAME ) )
{
it.remove();
}
}
doc.getRootElement()
.addContent( new Comment( " Modified by " + com.redhat.rcm.version.stats.VersionInfo.APP_NAME
+ " version " + com.redhat.rcm.version.stats.VersionInfo.APP_VERSION + " ("
+ com.redhat.rcm.version.stats.VersionInfo.APP_COMMIT_ID + ") " ) );
doc.getRootElement()
.addContent( "\n" );
session.getLog( pom )
.add( "Writing modified POM: %s", out );
writer = WriterFactory.newWriter( out, encoding );
final List<?> rootComments = doc.getContent ( new ContentFilter( ContentFilter.COMMENT ) );
XMLOutputter xmlo = new XMLOutputter ( format )
{
@Override
protected void printComment(Writer out, Comment comment)
throws IOException {
super.printComment (out, comment);
// If root level comments exist and is the current Comment object
// output an extra newline to tidy the output
if (rootComments.contains( comment) )
{
out.write(System.getProperty( "line.separator" ));
}
}
};
xmlo.output( doc, writer );
if ( relocatePom && !out.equals( pom ) )
{
session.getLog( pom )
.add( "Deleting original POM: %s\nPurging unused directories...", pom );
pom.delete();
File dir = pom.getParentFile();
while ( dir != null && !basedir.equals( dir ) )
{
final String[] listing = dir.list();
if ( listing == null || listing.length < 1 )
{
dir.delete();
dir = dir.getParentFile();
}
else
{
break;
}
}
}
}
catch ( final IOException e )
{
session.addError( new VManException( "Failed to write modified POM: %s to: %s\n\tReason: %s", e, pom, out,
e.getMessage() ) );
}
catch ( final JDOMException e )
{
session.addError( new VManException( "Failed to read original POM for rewrite: %s\n\tReason: %s", e, pom,
e.getMessage() ) );
}
finally
{
IOUtil.close( writer );
}
return out;
}
private static void normalizeModel( final Model model )
{
final Set<ModelBase> bases = new HashSet<ModelBase>();
bases.add( model );
final List<Profile> profiles = model.getProfiles();
if ( profiles != null )
{
bases.addAll( profiles );
}
}
/**
* @param project
* @param ns
*/
private static void normalizeNamespace( final Document doc )
{
final Namespace ns = Namespace.getNamespace( "http://maven.apache.org/POM/4.0.0" );
final Element project = doc.getRootElement();
if ( !ns.equals( project.getNamespace() ) )
{
project.setNamespace( ns );
}
Namespace xsi = project.getNamespace( "xsi" );
if ( xsi == null )
{
xsi = Namespace.getNamespace( "xsi", "http://www.w3.org/2001/XMLSchema-instance" );
project.addNamespaceDeclaration( xsi );
}
Attribute schemaLocation = project.getAttribute( "schemaLocation", ns );
if ( schemaLocation == null )
{
schemaLocation =
new Attribute( "schemaLocation",
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd", xsi );
project.setAttribute( schemaLocation );
}
try
{
@SuppressWarnings( "unchecked" )
final List<Element> allNodes = XPath.selectNodes( project, "//*" );
for ( final Element node : allNodes )
{
if ( node.getNamespace() == null || node.getNamespace() == Namespace.NO_NAMESPACE
|| node.getNamespace()
.getURI()
.trim()
.length() < 1 )
{
node.setNamespace( ns );
}
}
}
catch ( final JDOMException e )
{
logger.error( "Failed to select all nodes in the document.", e );
}
}
public static File generateRelocatedPomFile( final ProjectKey coord, final String version, final File basedir )
{
final StringBuilder pathBuilder = new StringBuilder();
pathBuilder.append( coord.getGroupId()
.replace( '.', separatorChar ) )
.append( separatorChar )
.append( coord.getArtifactId() )
.append( separatorChar )
.append( version )
.append( separatorChar )
.append( coord.getArtifactId() )
.append( '-' )
.append( version )
.append( ".pom" );
final File out = new File( basedir, pathBuilder.toString() );
final File outDir = out.getParentFile();
outDir.mkdirs();
return out;
}
}