/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr 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.
*
* Structr 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 Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.schema.importer;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.structr.common.CaseHelper;
import org.structr.common.error.FrameworkException;
import org.structr.core.graph.MaintenanceCommand;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
/**
*
*
*/
public class RDFImporter extends SchemaImporter implements MaintenanceCommand {
private static final Logger logger = LoggerFactory.getLogger(RDFImporter.class.getName());
private static String separator = null;
@Override
public void execute(final Map<String, Object> attributes) throws FrameworkException {
final String fileName = (String)attributes.get("file");
final String source = (String)attributes.get("source");
final String url = (String)attributes.get("url");
separator = (String)attributes.get("separator");
if (StringUtils.isBlank(separator)) {
separator = "#";
}
if (fileName == null && source == null && url == null) {
throw new FrameworkException(422, "Please supply file, url or source parameter.");
}
if (fileName != null && source != null) {
throw new FrameworkException(422, "Please supply only one of file, url or source.");
}
if (fileName != null && url != null) {
throw new FrameworkException(422, "Please supply only one of file, url or source.");
}
if (url != null && source != null) {
throw new FrameworkException(422, "Please supply only one of file, url or source.");
}
try {
if (fileName != null) {
try (final FileInputStream fis = new FileInputStream(fileName)) {
importCypher(importRDF(fis));
}
} else if (url != null) {
try (final InputStream is = new URL(url).openStream()) {
importCypher(importRDF(is));
}
} else if (source != null) {
try (final InputStream bis = new ByteArrayInputStream(source.getBytes())) {
importCypher(importRDF(bis));
}
}
} catch (Throwable t) {
logger.warn("", t);
}
analyzeSchema();
}
@Override
public boolean requiresEnclosingTransaction() {
return true;
}
@Override
public boolean requiresFlushingOfCaches() {
return false;
}
public List<String> importRDF(final InputStream is) throws IOException, ParserConfigurationException, SAXException, XPathExpressionException {
final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
final Set<HasSubclassRelationship> subclasses = new LinkedHashSet<>();
final Map<String, RdfProperty> properties = new LinkedHashMap<>();
final Map<String, RdfClass> classes = new LinkedHashMap<>();
final Set<Triple> triples = new LinkedHashSet<>();
final StringBuilder cypher = new StringBuilder();
final List<String> cypherStatements = new LinkedList<>();
for (Node node = doc.getElementsByTagName("rdfs:Class").item(0); node != null; node = node.getNextSibling()) {
final String type = node.getNodeName();
if ("rdfs:Class".equals(type)) {
handleClass(classes, subclasses, node);
}
if ("rdf:Property".equals(type)) {
handleProperty(properties, triples, node);
}
}
for (final Entry<String, RdfClass> entry : classes.entrySet()) {
final Set<String> superclasses = new LinkedHashSet<>();
final String id = entry.getKey();
final RdfClass rdfClass = entry.getValue();
superclasses.add(rdfClass.name);
for (final HasSubclassRelationship rel : subclasses) {
if (rel.child.equals(id)) {
superclasses.add(classes.get(rel.parent).id);
}
}
cypher.append("CREATE (").append(rdfClass.id.replaceAll("[\\W_]+", ""));
for (final String cl : superclasses) {
cypher.append(":").append(rdfClass.type);
}
cypher.append(" { ");
cypher.append("name: '").append(rdfClass.name).append("' ");
if (StringUtils.isNotBlank(rdfClass.comment)) { cypher.append(", comment: '").append(rdfClass.comment).append("' "); }
cypher.append("})\n");
}
for (final Triple triple : triples) {
if (!triple.relationship.endsWith("i") && !triple.target.startsWith("http")) {
cypher.append("CREATE (").append(triple.source).append("-[:");
final String relType = properties.get(triple.relationship).id;
cypher.append(CaseHelper.toLowerCamelCase(relType).replaceAll("[\\W_]+", ""));
cypher.append("]->");
cypher.append(triple.target);
cypher.append(")\n");
}
}
// we need to put all the statements in a single element because
// the individual elements are executed separately and there are
// cross references in the output of this importer.
logger.info(cypher.toString());
cypherStatements.add(cypher.toString());
return cypherStatements;
}
private static void handleClass(final Map<String, RdfClass> classes, final Set<HasSubclassRelationship> subclasses, final Node classNode) {
final NamedNodeMap attributes = classNode.getAttributes();
final String comment = getChildString(classNode, "rdfs:comment");
final String rawName = getString(attributes, "rdf:ID") != null ? getString(attributes, "rdf:ID") : getString(attributes, "rdf:about");
classes.put(rawName, new RdfClass(rawName, comment));
for (Node node = classNode.getChildNodes().item(0); node != null; node = node.getNextSibling()) {
final String type = node.getNodeName();
if ("rdfs:subClassOf".equals(type)) {
final String parent = handleSubclass(classes, node);
subclasses.add(new HasSubclassRelationship(parent, rawName));
}
}
}
private static String handleSubclass(final Map<String, RdfClass> classes, final Node classNode) {
final NamedNodeMap attributes = classNode.getAttributes();
final String comment = getChildString(classNode, "rdfs:comment");
final String rawName = getString(attributes, "rdf:resource");
classes.put(rawName, new RdfClass(rawName, comment));
return rawName;
}
private static void handleProperty(final Map<String, RdfProperty> properties, final Set<Triple> triples, final Node propertyNode) {
final NamedNodeMap attributes = propertyNode.getAttributes();
final String comment = getChildString(propertyNode, "rdfs:comment");
final String rawName = getString(attributes, "rdf:ID") != null ? getString(attributes, "rdf:ID") : getString(attributes, "rdf:about");
properties.put(rawName, new RdfProperty(rawName, comment));
String domain = null;
String range = null;
for (Node node = propertyNode.getChildNodes().item(0); node != null; node = node.getNextSibling()) {
final String type = node.getNodeName();
if ("rdfs:domain".equals(type)) {
domain = handleDomain(node);
}
if ("rdfs:range".equals(type)) {
range = handleRange(node);
}
}
triples.add(new Triple(domain, rawName, range));
}
private static String handleDomain(final Node propertyNode) {
final NamedNodeMap attributes = propertyNode.getAttributes();
final String rawName = getString(attributes, "rdf:resource");
final int position = rawName.lastIndexOf(separator);
if (position >= 0) {
final String id = rawName.substring(0, position);
return id;
} else {
return rawName;
}
}
private static String handleRange(final Node propertyNode) {
final NamedNodeMap attributes = propertyNode.getAttributes();
final String rawName = getString(attributes, "rdf:resource");
final int position = rawName.lastIndexOf(separator);
if (position >= 0) {
final String id = rawName.substring(0, position);
return id;
} else {
return rawName;
}
}
private static String getString(final NamedNodeMap attributes, final String key) {
final Node node = attributes.getNamedItem(key);
return node != null ? attributes.getNamedItem(key).getNodeValue() : null;
}
private static String getChildString(final Node parent, final String key) {
for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
if (key.equals(child.getNodeName())) {
return child.getTextContent();
}
}
return null;
}
private static class HasSubclassRelationship {
public String parent = null;
public String child = null;
public HasSubclassRelationship(final String parent, final String child) {
this.parent = parent;
this.child = child;
}
@Override
public String toString() {
return parent + "->" + child;
}
}
private static class RdfClass {
public String comment = null;
public String name = null;
public String id = null;
public String type = null;
public RdfClass(final String rawAttribueValue, final String comment) {
final int position = rawAttribueValue.lastIndexOf(separator);
this.name = rawAttribueValue;
this.id = rawAttribueValue.replaceAll("[\\W_]+", "");
this.type = rawAttribueValue.substring(position+1).replaceAll("[\\W_]+", "");
this.comment = comment;
}
}
private static class RdfProperty {
public String comment = null;
public String name = null;
public String id = null;
public String type = null;
public RdfProperty(final String rawAttribueValue, final String comment) {
final int position = rawAttribueValue.lastIndexOf(separator);
this.name = rawAttribueValue;
this.id = rawAttribueValue.replaceAll("[\\W_]+", "");
this.type = rawAttribueValue.substring(position+1).replaceAll("[\\W_]+", "");
this.comment = comment;
}
}
private static class Triple {
public String relationship = null;
public String source = null;
public String target = null;
public Triple(final String source, final String property, final String target) {
this.relationship = property;
this.source = source;
this.target = target;
}
@Override
public String toString() {
return source + "-[" + relationship + "]->" + target;
}
}
}