/*******************************************************************************
* Copyright (c) 2015 IBH SYSTEMS GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBH SYSTEMS GmbH - initial API and implementation
*******************************************************************************/
package org.eclipse.packagedrone.repo.adapter.rpm.yum;
import static org.eclipse.packagedrone.repo.XmlHelper.addElement;
import static org.eclipse.packagedrone.repo.XmlHelper.addOptionalElement;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
import org.eclipse.packagedrone.repo.XmlHelper;
import org.eclipse.packagedrone.repo.adapter.rpm.RpmInformation;
import org.eclipse.packagedrone.repo.adapter.rpm.RpmInformation.Changelog;
import org.eclipse.packagedrone.repo.adapter.rpm.RpmInformation.Dependency;
import org.eclipse.packagedrone.repo.aspect.common.spool.OutputSpooler;
import org.eclipse.packagedrone.repo.aspect.common.spool.SpoolOutTarget;
import org.eclipse.packagedrone.repo.channel.ArtifactInformation;
import org.eclipse.packagedrone.utils.io.IOConsumer;
import org.eclipse.packagedrone.utils.rpm.RpmDependencyFlags;
import org.eclipse.packagedrone.utils.rpm.RpmVersion;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class RepositoryCreator
{
private final XmlHelper xml = new XmlHelper ();
private final OutputSpooler primaryStreamBuilder;
private final OutputSpooler filelistsStreamBuilder;
private final OutputSpooler otherStreamBuilder;
private final OutputSpooler mdStreamBuilder;
private final List<Pattern> primaryFiles;
private final List<Pattern> primaryDirs;
public interface Context
{
public void addPackage ( final String sha1, final ArtifactInformation artifact, final RpmInformation info );
}
public class ContextImpl implements Context
{
private final OutputStream primaryStream;
private final OutputStream filelistsStream;
private final OutputStream otherStream;
private final Document primary;
private final Document filelists;
private final Document other;
private final XmlHelper xml;
private final Element primaryRoot;
private final Element filelistsRoot;
private final Element otherRoot;
private long count;
public ContextImpl ( final OutputStream primaryStream, final OutputStream filelistsStream, final OutputStream otherStream, final XmlHelper xml )
{
this.primaryStream = primaryStream;
this.filelistsStream = filelistsStream;
this.otherStream = otherStream;
this.primary = xml.createNs ();
this.primaryRoot = this.primary.createElementNS ( "http://linux.duke.edu/metadata/common", "metadata" );
this.primaryRoot.setAttribute ( "xmlns:rpm", "http://linux.duke.edu/metadata/rpm" );
this.primary.appendChild ( this.primaryRoot );
this.filelists = xml.createNs ();
this.filelistsRoot = this.filelists.createElementNS ( "http://linux.duke.edu/metadata/filelists", "filelists" );
this.filelists.appendChild ( this.filelistsRoot );
this.other = xml.createNs ();
this.otherRoot = this.other.createElementNS ( "http://linux.duke.edu/metadata/other", "otherdata" );
this.other.appendChild ( this.otherRoot );
this.xml = xml;
}
@Override
public void addPackage ( final String sha1, final ArtifactInformation artifact, final RpmInformation info )
{
if ( info == null )
{
return;
}
this.count++;
// insert to primary
insertToPrimary ( sha1, artifact, info );
// insert to "filelists"
{
final Element pkg = createPackage ( this.filelistsRoot, sha1, info );
appendFiles ( info, pkg, null, null );
}
// insert to "other"
{
final Element pkg = createPackage ( this.otherRoot, sha1, info );
for ( final Changelog log : info.getChangelog () )
{
final Element cl = addElement ( pkg, "changelog", log.getText () );
cl.setAttribute ( "author", log.getAuthor () );
cl.setAttribute ( "date", "" + log.getTimestamp () );
}
}
}
private void appendFiles ( final RpmInformation info, final Element pkg, final Predicate<String> fileFilter, final Predicate<String> dirFilter )
{
for ( final String file : new TreeSet<> ( info.getFiles () ) )
{
if ( fileFilter == null || fileFilter.test ( file ) )
{
addElement ( pkg, "file", file );
}
}
for ( final String dir : new TreeSet<> ( info.getDirectories () ) )
{
if ( dirFilter == null || dirFilter.test ( dir ) )
{
final Element ele = addElement ( pkg, "file", dir );
ele.setAttribute ( "type", "dir" );
}
}
}
private void insertToPrimary ( final String sha1, final ArtifactInformation artifact, final RpmInformation info )
{
final Element pkg = addElement ( this.primaryRoot, "package" );
pkg.setAttribute ( "type", "rpm" );
addElement ( pkg, "name", info.getName () );
addElement ( pkg, "arch", info.getArchitecture () );
addVersion ( pkg, info.getVersion () );
final Element checksum = addElement ( pkg, "checksum", sha1 );
checksum.setAttribute ( "type", "sha" );
checksum.setAttribute ( "pkgid", "YES" );
addElement ( pkg, "summary", info.getSummary () );
addElement ( pkg, "description", info.getDescription () );
addElement ( pkg, "packager", info.getPackager () );
addElement ( pkg, "url", info.getUrl () );
// time
final Element time = addElement ( pkg, "time" );
time.setAttribute ( "file", "" + artifact.getCreationTimestamp ().getTime () / 1000 );
if ( info.getBuildTimestamp () != null )
{
time.setAttribute ( "build", "" + info.getBuildTimestamp () );
}
// size
final Element size = addElement ( pkg, "size" );
size.setAttribute ( "package", "" + artifact.getSize () );
if ( info.getInstalledSize () != null )
{
size.setAttribute ( "installed", "" + info.getInstalledSize () );
}
if ( info.getArchiveSize () != null )
{
size.setAttribute ( "archive", "" + info.getArchiveSize () );
}
// location
final Element location = addElement ( pkg, "location" );
location.setAttribute ( "href", String.format ( "pool/%s/%s", artifact.getId (), artifact.getName () ) );
// add format section
final Element fmt = addElement ( pkg, "format" );
addOptionalElement ( fmt, "rpm:license", info.getLicense () );
addOptionalElement ( fmt, "rpm:vendor", info.getVendor () );
addOptionalElement ( fmt, "rpm:group", info.getGroup () );
addOptionalElement ( fmt, "rpm:buildhost", info.getBuildHost () );
addOptionalElement ( fmt, "rpm:sourcerpm", info.getSourcePackage () );
// add header range
final Element rng = addElement ( fmt, "rpm:header-range" );
rng.setAttribute ( "start", "" + info.getHeaderStart () );
rng.setAttribute ( "end", "" + info.getHeaderEnd () );
addDependencies ( fmt, "rpm:provides", info.getProvides () );
addDependencies ( fmt, "rpm:requires", info.getRequires () );
addDependencies ( fmt, "rpm:conflicts", info.getConflicts () );
addDependencies ( fmt, "rpm:obsoletes", info.getObsoletes () );
// add primary files
appendFiles ( info, pkg, file -> matches ( file, RepositoryCreator.this.primaryFiles ), dir -> matches ( dir, RepositoryCreator.this.primaryDirs ) );
}
private void addDependencies ( final Element fmt, final String elementName, final List<Dependency> deps )
{
final Element ele = addElement ( fmt, elementName );
for ( final Dependency dep : deps )
{
final EnumSet<RpmDependencyFlags> flags = RpmDependencyFlags.parse ( dep.getFlags () );
if ( flags.contains ( RpmDependencyFlags.RPMLIB ) )
{
continue;
}
final Element entry = addElement ( ele, "rpm:entry" );
entry.setAttribute ( "name", dep.getName () );
if ( dep.getVersion () != null )
{
final RpmVersion version = RpmVersion.valueOf ( dep.getVersion () );
entry.setAttribute ( "epoch", "" + version.getEpoch ().orElse ( 0 ) );
entry.setAttribute ( "ver", version.getVersion () );
if ( version.getRelease ().isPresent () )
{
entry.setAttribute ( "rel", version.getRelease ().get () );
}
}
final boolean eq = flags.contains ( RpmDependencyFlags.EQUAL );
if ( flags.contains ( RpmDependencyFlags.GREATER ) )
{
entry.setAttribute ( "flags", eq ? "GE" : "GT" );
}
else if ( flags.contains ( RpmDependencyFlags.LESS ) )
{
entry.setAttribute ( "flags", eq ? "LE" : "LT" );
}
else if ( eq )
{
entry.setAttribute ( "flags", "EQ" );
}
final boolean pre = flags.contains ( RpmDependencyFlags.PREREQ ) || flags.contains ( RpmDependencyFlags.SCRIPT_PRE ) || flags.contains ( RpmDependencyFlags.SCRIPT_POST );
if ( pre )
{
entry.setAttribute ( "pre", "1" );
}
}
}
private Element createPackage ( final Element root, final String id, final RpmInformation info )
{
final Element pkg = addElement ( root, "package" );
pkg.setAttribute ( "pkgid", id );
pkg.setAttribute ( "name", info.getName () );
pkg.setAttribute ( "arch", info.getArchitecture () );
addVersion ( pkg, info.getVersion () );
return pkg;
}
private Element addVersion ( final Element pkg, final RpmInformation.Version version )
{
if ( version == null )
{
return null;
}
final Element ver = addElement ( pkg, "version" );
if ( version.getEpoch () == null || version.getEpoch ().isEmpty () )
{
ver.setAttribute ( "epoch", "0" );
}
else
{
ver.setAttribute ( "epoch", version.getEpoch () );
}
ver.setAttribute ( "ver", version.getVersion () );
ver.setAttribute ( "rel", version.getRelease () );
return ver;
}
public void close () throws IOException
{
this.primaryRoot.setAttribute ( "packages", "" + this.count );
this.filelistsRoot.setAttribute ( "packages", "" + this.count );
this.otherRoot.setAttribute ( "packages", "" + this.count );
try
{
this.xml.write ( this.primary, this.primaryStream );
this.xml.write ( this.filelists, this.filelistsStream );
this.xml.write ( this.other, this.otherStream );
}
catch ( final IOException e )
{
throw e;
}
catch ( final Exception e )
{
throw new IOException ( e );
}
}
}
public RepositoryCreator ( final SpoolOutTarget target )
{
// filters
final String dirFilter = System.getProperty ( "drone.rpm.yum.primaryDirs", "bin/,^/etc/" );
final String fileFilter = System.getProperty ( "drone.rpm.yum.primaryFiles", dirFilter );
this.primaryFiles = Arrays.stream ( fileFilter.split ( "," ) ).map ( re -> Pattern.compile ( re ) ).collect ( Collectors.toList () );
this.primaryDirs = Arrays.stream ( dirFilter.split ( "," ) ).map ( re -> Pattern.compile ( re ) ).collect ( Collectors.toList () );
// primary
this.primaryStreamBuilder = new OutputSpooler ( target );
this.primaryStreamBuilder.addDigest ( "SHA1" );
this.primaryStreamBuilder.addOutput ( "repodata/primary.xml", "application/xml" );
this.primaryStreamBuilder.addOutput ( "repodata/primary.xml.gz", "application/x-gzip", output -> new GZIPOutputStream ( output ) );
// filelists
this.filelistsStreamBuilder = new OutputSpooler ( target );
this.filelistsStreamBuilder.addDigest ( "SHA1" );
this.filelistsStreamBuilder.addOutput ( "repodata/filelists.xml", "application/xml" );
this.filelistsStreamBuilder.addOutput ( "repodata/filelists.xml.gz", "application/x-gzip", output -> new GZIPOutputStream ( output ) );
// other
this.otherStreamBuilder = new OutputSpooler ( target );
this.otherStreamBuilder.addDigest ( "SHA1" );
this.otherStreamBuilder.addOutput ( "repodata/other.xml", "application/xml" );
this.otherStreamBuilder.addOutput ( "repodata/other.xml.gz", "application/x-gzip", output -> new GZIPOutputStream ( output ) );
// md
this.mdStreamBuilder = new OutputSpooler ( target );
this.mdStreamBuilder.addOutput ( "repodata/repomd.xml", "application/xml" );
}
public boolean matches ( final String pathName, final List<Pattern> filterList )
{
for ( final Pattern p : filterList )
{
if ( p.matcher ( pathName ).find () )
{
return true;
}
}
return false;
}
public void process ( final IOConsumer<Context> consumer ) throws IOException
{
final long now = System.currentTimeMillis ();
this.primaryStreamBuilder.open ( primaryStream -> {
this.filelistsStreamBuilder.open ( filelistsStream -> {
this.otherStreamBuilder.open ( otherStream -> {
final ContextImpl ctx = makeContext ( primaryStream, filelistsStream, otherStream );
consumer.accept ( ctx );
ctx.close ();
} );
} );
} );
this.mdStreamBuilder.open ( stream -> {
writeRepoMd ( stream, now );
} );
}
private ContextImpl makeContext ( final OutputStream primaryStream, final OutputStream filelistsStream, final OutputStream otherStream )
{
return new ContextImpl ( primaryStream, filelistsStream, otherStream, this.xml );
}
private void writeRepoMd ( final OutputStream stream, final long now ) throws IOException
{
final Document doc = this.xml.createNs ();
final Element root = doc.createElementNS ( "http://linux.duke.edu/metadata/repo", "repomd" );
doc.appendChild ( root );
root.setAttribute ( "revision", "" + now / 1000 );
addDataFile ( root, this.primaryStreamBuilder, "primary", now );
addDataFile ( root, this.filelistsStreamBuilder, "filelists", now );
addDataFile ( root, this.otherStreamBuilder, "other", now );
try
{
this.xml.write ( doc, stream );
}
catch ( final Exception e )
{
throw new IOException ( e );
}
}
private void addDataFile ( final Element root, final OutputSpooler spooler, final String baseName, final long now )
{
final Element data = addElement ( root, "data" );
data.setAttribute ( "type", baseName );
final Element checksum = addElement ( data, "checksum", spooler.getChecksum ( "repodata/" + baseName + ".xml.gz", "SHA1" ) );
checksum.setAttribute ( "type", "sha" );
final Element openChecksum = addElement ( data, "open-checksum", spooler.getChecksum ( "repodata/" + baseName + ".xml", "SHA1" ) );
openChecksum.setAttribute ( "type", "sha" );
final Element location = addElement ( data, "location" );
location.setAttribute ( "href", "repodata/" + baseName + ".xml.gz" );
addElement ( data, "timestamp", now / 1000 );
addElement ( data, "size", "" + spooler.getSize ( "repodata/" + baseName + ".xml.gz" ) );
addElement ( data, "open-size", "" + spooler.getSize ( "repodata/" + baseName + ".xml" ) );
}
}