package org.dru.clay.respository.ivy;
import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.dru.clay.logger.Logger;
import org.dru.clay.logger.LoggerFactory;
import org.dru.clay.respository.Configuration;
import org.dru.clay.respository.artifact.Artifact;
import org.dru.clay.respository.artifact.Group;
import org.dru.clay.respository.artifact.Module;
import org.dru.clay.respository.artifact.UnresolvedArtifact;
import org.dru.clay.respository.artifact.VersionPattern;
import org.dru.clay.respository.dependency.ConfigurationMapping;
import org.dru.clay.respository.dependency.Dependency;
import org.dru.clay.respository.exception.ResolveException;
import org.dru.clay.util.xml.Xml;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
public class IvyXmlParser {
private final Logger logger = LoggerFactory.getLogger(IvyXmlParser.class);
// private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
private final Map<String, Configuration> configurations = new HashMap<String, Configuration>();
private final Set<Dependency> dependencies = new HashSet<Dependency>();
private final Map<String, String> configurationExtensionMap = new HashMap<String, String>();
private final ConfigurationMappingParser configurationParser = new ConfigurationMappingParser();
private IvyStatus status;
private String dependencyDefaultConf;
public IvyXmlParser() {
}
public IvyXml parse(Module module, byte[] content) {
try {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
int length = content.length;
while (length > 0 && content[length - 1] != '>') {
length--;
}
final DocumentBuilder builder = factory.newDocumentBuilder();
final Document document = builder.parse(new ByteArrayInputStream(content, 0, length));
final Element ivyModule = document.getDocumentElement();
verifyModuleInformation(module, ivyModule);
parseConfigurations(module, ivyModule);
parseArtifacts(module, ivyModule);
parseDependencies(module, ivyModule);
resolveExtensions();
return new IvyXml(module, status, configurations, dependencies);
} catch (Exception e) {
throw new ResolveException("Resolve error: " + e.getMessage(), e);
}
}
private void verifyModuleInformation(Module module, Element ivyModule) {
final Node info = Xml.getSingleChild(ivyModule, "info");
final String organisation = Xml.getAttribute(info, "organisation");
final String moduleName = Xml.getAttribute(info, "module");
final String revision = Xml.getAttribute(info, "revision");
this.status = Xml.getEnumAttribute(info, "status", IvyStatus.class, IvyStatus.Integration);
// final String publication = Xml.getAttribute(info, "publication");
if (!module.getGroup().get().equals(organisation)) {
throw new IllegalArgumentException("Ivy description of artifact has different organisation than the resolve artifact: "
+ organisation);
}
final Artifact artifact = module.getArtifact();
if (!artifact.getName().equals(moduleName)) {
throw new IllegalArgumentException("Ivy description of artifact has different module than the resolve artifact: " + moduleName);
}
if (!artifact.getVersion().plain().equals(revision)) {
throw new IllegalArgumentException("Ivy description of artifact has different revision than the resolve artifact: " + revision);
}
}
private void parseConfigurations(Module module, Element ivyModule) {
final Node configs = Xml.getSingleChild(ivyModule, "configurations");
dependencyDefaultConf = Xml.getAttribute(configs, "defaultConf", "");
for (Node node : Xml.getChildren(configs, "conf")) {
final String name = Xml.getAttribute(node, "name");
if (name == null) {
throw new IllegalArgumentException("Configuration is missing the required attribute 'name'");
}
final String description = Xml.getAttribute(node, "description");
final IvyVisibility visibility = Xml.getEnumAttribute(node, "visibility", IvyVisibility.class, IvyVisibility.Public);
final String extending = Xml.getAttribute(node, "extends", null);
// final boolean transitive = Xml.getBooleanAttribute(conf, "transitive", true);
// final String deprecated = Xml.getAttribute(conf, "deprecated");
final Configuration configuration = new Configuration(name, description, visibility);
configurations.put(name, configuration);
if (extending != null) {
configurationExtensionMap.put(name, extending);
}
}
}
private void parseArtifacts(Module module, Element ivyModule) {
final Node publications = Xml.getSingleChild(ivyModule, "publications");
final String defaultConf = Xml.getAttribute(publications, "defaultConf", "*");
for (Node node : Xml.getChildren(publications, "artifact")) {
final String name = Xml.getAttribute(node, "name", module.getArtifact().getName());
final String type = Xml.getAttribute(node, "type", "jar");
final String ext = Xml.getAttribute(node, "ext", type);
final String conf = Xml.getAttribute(node, "conf", defaultConf);
// final String url = Xml.getAttribute(node, "url", type);
final Artifact artifact = new Artifact(name, module.getArtifact().getVersion(), ext, null);
final StringTokenizer tokenizer = new StringTokenizer(conf);
while (tokenizer.hasMoreTokens()) {
final String token = tokenizer.nextToken().trim();
if ("*".equals(token)) {
// insert the artifact in all configurations
for (Configuration configuration : configurations.values()) {
configuration.addArtifact(artifact);
}
break;
} else {
final Configuration configuration = configurations.get(token);
if (configuration == null) {
throw new IllegalArgumentException("The artifact " + artifact + " specified unknown configuration " + token);
}
configuration.addArtifact(artifact);
}
}
}
}
private void parseDependencies(Module module, Element ivyModule) {
final Node deps = Xml.getSingleChild(ivyModule, "dependencies");
for (Node node : Xml.getChildren(deps, "dependency")) {
final Dependency dependency = parseDependency(module, node);
dependencies.add(dependency);
}
}
private Dependency parseDependency(Module module, Node node) {
final String group = Xml.getAttribute(node, "org", module.getGroup().get());
final String name = Xml.getAttribute(node, "name");
final String rev = Xml.getAttribute(node, "rev");
// final String revConstraint = Xml.getAttribute(node, "revConstraint", rev);
final Boolean transitive = Xml.getBooleanAttribute(node, "transitive", true);
String conf = Xml.getAttribute(node, "conf", dependencyDefaultConf);
if (name == null) {
throw new IllegalArgumentException("Dependency is missing attribute name");
}
if (rev == null) {
throw new IllegalArgumentException("Dependency " + name + " is missing attribute rev");
}
UnresolvedArtifact artifact = new UnresolvedArtifact(name, new VersionPattern(rev));
if (!Xml.getChildren(node, "conf").isEmpty()) {
logger.error("Dependency %s uses nested conf elements which isn't supported : using '*'", artifact);
conf = "*";
}
final ConfigurationMapping mapping = configurationParser.parse(conf);
return new Dependency(new Group(group), artifact, mapping, transitive);
}
private void resolveExtensions() {
for (Map.Entry<String, String> entry : configurationExtensionMap.entrySet()) {
final Configuration configuration = configurations.get(entry.getKey());
if (configuration == null) {
throw new IllegalArgumentException("Invalid configuration " + entry.getKey() + " used in configuration extension.");
}
final StringTokenizer tokenizer = new StringTokenizer(entry.getValue(), ",");
while (tokenizer.hasMoreTokens()) {
final String token = tokenizer.nextToken().trim();
final Configuration source = configurations.get(token);
if (source == null) {
throw new IllegalArgumentException("Invalid configuration " + token + " used in configuration extension.");
}
for (Artifact artifact : source.getArtifacts()) {
configuration.addArtifact(artifact);
}
}
}
}
}