package de.cinovo.cloudconductor.server.repo.indexer;
import java.io.IOException;
import java.io.InputStream;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.GZIPInputStream;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import de.cinovo.cloudconductor.api.DependencyType;
import de.cinovo.cloudconductor.api.model.Dependency;
import de.cinovo.cloudconductor.api.model.PackageVersion;
import de.cinovo.cloudconductor.server.repo.RepoEntry;
import de.cinovo.cloudconductor.server.repo.provider.IRepoProvider;
/**
* Copyright 2014 Hoegernet<br>
* <br>
*
* @author Thorsten Hoeger
*
*/
public class RPMIndexer implements IRepoIndexer {
private static enum RPMPrimaryState {
Repo, Package, Requires, Provides, Conflicts;
public static EnumSet<RPMPrimaryState> depStates = EnumSet.of(Requires, Provides, Conflicts);
}
private static class RPMPrimaryParser extends DefaultHandler {
private Set<PackageVersion> versions = new HashSet<PackageVersion>();
private String name;
private String version;
private Set<Dependency> dependencies;
private String tmpValue;
private RPMPrimaryState state = RPMPrimaryState.Repo;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if ((this.state == RPMPrimaryState.Repo) && qName.equals("package") && attributes.getValue("type").equals("rpm")) {
this.state = RPMPrimaryState.Package;
this.name = null;
this.version = null;
this.dependencies = new HashSet<Dependency>();
} else if ((this.state == RPMPrimaryState.Package) && qName.equals("version")) {
this.version = String.format("%s-%s", attributes.getValue("ver"), attributes.getValue("rel"));
} else if ((this.state == RPMPrimaryState.Package) && qName.equals("rpm:requires")) {
this.state = RPMPrimaryState.Requires;
} else if ((this.state == RPMPrimaryState.Package) && qName.equals("rpm:provides")) {
this.state = RPMPrimaryState.Provides;
} else if ((this.state == RPMPrimaryState.Package) && qName.equals("rpm:conflicts")) {
this.state = RPMPrimaryState.Conflicts;
} else if (RPMPrimaryState.depStates.contains(this.state) && qName.equals("rpm:entry")) {
// example: <rpm:entry name="jdk" flags="GE" epoch="0" ver="1.7"/>
String depName = attributes.getValue("name");
String depVersion = attributes.getValue("ver");
if (depVersion == null) {
depVersion = "";
}
String depOperator = this.parseOperator(attributes.getValue("flags"));
String depType = this.convertDepType(this.state);
Dependency dep = new Dependency(depName, depVersion, depOperator, depType);
this.dependencies.add(dep);
}
}
private String parseOperator(String flag) {
if (flag == null) {
return "";
}
switch (flag) {
case "GE":
return ">=";
case "EQ":
return "=";
case "LE":
return "<=";
default:
return "";
}
}
private String convertDepType(RPMPrimaryState depState) {
switch (depState) {
case Conflicts:
return DependencyType.CONFLICTS.name();
case Provides:
return DependencyType.PROVIDES.name();
case Requires:
return DependencyType.REQUIRES.name();
default:
return "";
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ((this.state == RPMPrimaryState.Package) && qName.equals("name")) {
this.name = this.tmpValue;
} else if ((this.state == RPMPrimaryState.Package) && qName.equals("package")) {
PackageVersion pv = new PackageVersion(this.name, this.version, this.dependencies);
this.versions.add(pv);
this.state = RPMPrimaryState.Repo;
} else if ((this.state == RPMPrimaryState.Requires) && qName.equals("rpm:requires")) {
this.state = RPMPrimaryState.Package;
} else if ((this.state == RPMPrimaryState.Provides) && qName.equals("rpm:provides")) {
this.state = RPMPrimaryState.Package;
} else if ((this.state == RPMPrimaryState.Conflicts) && qName.equals("rpm:conflicts")) {
this.state = RPMPrimaryState.Package;
}
}
@Override
public void endDocument() throws SAXException {
if (this.state != RPMPrimaryState.Repo) {
throw new SAXException("Invalid end state: " + this.state);
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
this.tmpValue = new String(ch, start, length);
}
}
private static final String REPO_INDEX = "repodata/repomd.xml";
private RepoEntry latest;
@Override
public Set<PackageVersion> getRepoIndex(IRepoProvider provider) {
RepoEntry entry = provider.getEntry(RPMIndexer.REPO_INDEX);
if (entry != null) {
if (!entry.hasChanged(this.latest)) {
return null;
}
this.latest = entry;
Document repoXML = this.xmlDOM(provider.getEntryStream(RPMIndexer.REPO_INDEX));
XPath xpath = XPathFactory.newInstance().newXPath();
try {
String primaryHREF = xpath.evaluate("/repomd/data[@type='primary']/location/@href", repoXML);
GZIPInputStream gzipInputStream = new GZIPInputStream(provider.getEntryStream(primaryHREF));
RPMPrimaryParser handler = new RPMPrimaryParser();
this.xmlSAX(gzipInputStream, handler);
return handler.versions;
} catch (XPathExpressionException e) {
throw new RuntimeException("Failed to parse repomd.xml", e);
} catch (IOException e) {
throw new RuntimeException("Failed to read repodata", e);
}
}
throw new RuntimeException("Didn't find index file");
}
private Document xmlDOM(InputStream xmlStream) {
try {
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlStream);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void xmlSAX(InputStream xmlStream, DefaultHandler handler) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.newSAXParser().parse(xmlStream, handler);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}