diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpNode.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpNode.java index 8170d358f..0060b2a13 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpNode.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpNode.java @@ -2,6 +2,9 @@ package edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import javax.json.JsonObject; import javax.json.JsonString; @@ -14,7 +17,7 @@ import org.apache.jena.riot.out.EscapeStr; public abstract class DumpNode { public static DumpNode fromJson(JsonObject json) throws BadNodeException { if (json == null) { - return null; + return new DumpNullNode(); } String type = getString(json, "type"); @@ -32,23 +35,106 @@ public abstract class DumpNode { } } + private static final String PATTERN_NQUAD_URI = "<(.+)>"; + private static final String PATTERN_NQUAD_LITERAL = "" // + + "\"(.*)\"" // quoted string + + "(@(.+))?" // optional language specifier + + "(\\^\\^<(.+)>)?" // optional datatype + ; + private static final String PATTERN_NQUAD_BLANK = "_:(.+)"; + + public static DumpNode fromNquad(String text) throws BadNodeException { + if (text == null) { + return new DumpNullNode(); + } + + text = text.trim(); + if (text.isEmpty()) { + return new DumpNullNode(); + } + + Matcher m1 = Pattern.compile(PATTERN_NQUAD_URI).matcher(text); + Matcher m2 = Pattern.compile(PATTERN_NQUAD_LITERAL).matcher(text); + Matcher m3 = Pattern.compile(PATTERN_NQUAD_BLANK).matcher(text); + + if (m1.matches()) { + return new DumpUriNode(unescape(m1.group(1))); + } else if (m2.matches()) { + return new DumpLiteralNode(unescape(m2.group(1)), + unescape(m2.group(3)), unescape(m2.group(5))); + } else if (m3.matches()) { + return new DumpBlankNode(unescape(m3.group(1))); + } else { + throw new BadNodeException("Can't parse node: '" + text + "'"); + } + } + + private static String unescape(String s) { + return (s == null) ? null : EscapeStr.unescapeStr(s); + } + private static String getString(JsonObject json, String name) { JsonString jsString = json.getJsonString(name); return (jsString == null) ? null : json.getString(name); } + public abstract String getValue(); + public abstract String toNquad(); + public boolean isNull() { + return false; + } + + public boolean isUri() { + return false; + } + + public boolean isLiteral() { + return false; + } + + public boolean isBlank() { + return false; + } + + public static class DumpNullNode extends DumpNode { + @Override + public boolean isNull() { + return true; + } + + @Override + public String getValue() { + return null; + } + + @Override + public String toNquad() { + return ""; + } + } + public static class DumpUriNode extends DumpNode { private final String uri; - public DumpUriNode(String uri) throws BadNodeException { + private DumpUriNode(String uri) throws BadNodeException { if (uri == null) { throw new BadNodeException("uri may not be null."); } this.uri = uri; } + @Override + public boolean isUri() { + return true; + } + + @Override + public String getValue() { + return uri; + } + @Override public String toNquad() { return "<" + EscapeStr.stringEsc(uri) + ">"; @@ -60,7 +146,7 @@ public abstract class DumpNode { private final String language; private final String datatype; - public DumpLiteralNode(String value, String language, String datatype) + private DumpLiteralNode(String value, String language, String datatype) throws BadNodeException { if (value == null) { throw new BadNodeException("value may not be null."); @@ -74,6 +160,16 @@ public abstract class DumpNode { this.datatype = datatype; } + @Override + public boolean isLiteral() { + return true; + } + + @Override + public String getValue() { + return value; + } + @Override public String toNquad() { String valueString = "\"" + EscapeStr.stringEsc(value) + "\""; @@ -91,13 +187,23 @@ public abstract class DumpNode { public static class DumpBlankNode extends DumpNode { private final String label; - public DumpBlankNode(String label) throws BadNodeException { + private DumpBlankNode(String label) throws BadNodeException { if (label == null) { throw new BadNodeException("label may not be null."); } this.label = label; } + @Override + public boolean isBlank() { + return true; + } + + @Override + public String getValue() { + return label; + } + @Override public String toNquad() { return "_:" + label; @@ -113,4 +219,5 @@ public abstract class DumpNode { super(message, cause); } } + } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpParser.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpParser.java new file mode 100644 index 000000000..c359b7871 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpParser.java @@ -0,0 +1,24 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore; + +import java.io.IOException; + +/** + * The interface for parsers that process dump/restore files. + */ +public interface DumpParser extends AutoCloseable, Iterable { + @Override + public void close() throws IOException; + + public static class BadInputException extends RuntimeException { + public BadInputException(String message) { + super(message); + } + + public BadInputException(String message, Throwable cause) { + super(message, cause); + } + + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpQuad.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpQuad.java new file mode 100644 index 000000000..1b753f7b0 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpQuad.java @@ -0,0 +1,25 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore; + +/** + * TODO + */ +public class DumpQuad { + private final DumpTriple triple; + private final DumpNode g; + + public DumpQuad(DumpNode s, DumpNode p, DumpNode o, DumpNode g) { + this.triple = new DumpTriple(s, p, o); + this.g = g; + } + + public DumpTriple getTriple() { + return triple; + } + + public DumpNode getG() { + return g; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpRestoreController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpRestoreController.java index 8007f14f9..83aad120a 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpRestoreController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpRestoreController.java @@ -3,6 +3,7 @@ package edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.Map; @@ -21,6 +22,7 @@ import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService.ResultFormat; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; /** * Allow the user to dump the knowledge base from either RDFService, or restore @@ -43,6 +45,7 @@ public class DumpRestoreController extends FreemarkerHttpServlet { static final String PARAMETER_WHICH = "which"; static final String PARAMETER_FORMAT = "format"; static final String PARAMETER_SOURCE_FILE = "sourceFile"; + static final String PARAMETER_PURGE = "purge"; static final String ATTRIBUTE_TRIPLE_COUNT = "tripleCount"; private static final String TEMPLATE_NAME = "datatools-dumpRestore.ftl"; @@ -53,7 +56,8 @@ public class DumpRestoreController extends FreemarkerHttpServlet { */ @Override public long maximumMultipartFileSize() { - return 100L * 1024L * 1024L * 1024L; // allow really big uploads. + long gigabyte = 1024L * 1024L * 1024L; + return 100L * gigabyte; // permit really big uploads. } @Override @@ -94,9 +98,8 @@ public class DumpRestoreController extends FreemarkerHttpServlet { } else { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } - } catch (BadRequestException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + } catch (BadRequestException | RDFServiceException e) { + throw new RuntimeException(e); } } @@ -164,7 +167,14 @@ public class DumpRestoreController extends FreemarkerHttpServlet { * The formats that we will accept on a restore request. */ enum RestoreFormat { - NQUADS + NQUADS { + @Override + public DumpParser getParser(InputStream is) throws IOException { + return new NquadsParser(is); + } + }; + + public abstract DumpParser getParser(InputStream is) throws IOException; } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpTriple.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpTriple.java new file mode 100644 index 000000000..9bf874310 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/DumpTriple.java @@ -0,0 +1,35 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore; + +/** + * TODO + */ +public class DumpTriple { + private final DumpNode s; + private final DumpNode p; + private final DumpNode o; + + public DumpTriple(DumpNode s, DumpNode p, DumpNode o) { + this.s = s; + this.p = p; + this.o = o; + } + + public DumpNode getS() { + return s; + } + + public DumpNode getP() { + return p; + } + + public DumpNode getO() { + return o; + } + + public String toNtriples() { + return String.format("%s %s %s .\n", s.toNquad(), p.toNquad(), o.toNquad()); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NQuadLineSplitter.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NQuadLineSplitter.java new file mode 100644 index 000000000..0074e093b --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NQuadLineSplitter.java @@ -0,0 +1,117 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore.DumpNode.BadNodeException; + +/** + * A utility class for NquadsParser. Breaks an NQuad line into 3 or 4 strings, + * each holding the text representation of a node. + * + * It's a little tricky where literals are involved, because white space is + * significant, except between quotes, and quotes are only quotes if they aren't + * escaped. + */ +class NQuadLineSplitter { + private static final Log log = LogFactory.getLog(NQuadLineSplitter.class); + + private final String line; + private final List pieces = new ArrayList<>(); + private int i; + + public NQuadLineSplitter(String line) throws BadNodeException { + this.line = line; + while (!atEnd()) { + advancePastWhiteSpace(); + switch (line.charAt(i)) { + case '#': + advancePastComment(); + break; + case '<': + case '_': + scanUnquotedString(); + break; + case '"': + scanLiteralString(); + break; + case '.': + assureEndOfLine(); + break; + default: + throw new BadNodeException( + "found unexpected character at position " + i + + " of line '" + line + "'"); + } + } + } + + private boolean atEnd() { + return i >= line.length(); + } + + private boolean isWhiteSpace() { + return " \t\r\n".indexOf(line.charAt(i)) >= 0; + } + + private void advancePastWhiteSpace() { + while (!atEnd() && isWhiteSpace()) { + i++; + } + } + + private void advancePastComment() { + i = line.length(); + } + + private void scanUnquotedString() { + int start = i; + while (!atEnd() && !isWhiteSpace()) { + i++; + } + pieces.add(line.substring(start, i)); + } + + private void scanLiteralString() { + int start = i; + boolean inQuotes = false; + while (!atEnd() && (inQuotes || !isWhiteSpace())) { + if (isQuote()) { + inQuotes = !inQuotes; + log.debug("column " + i + ", inQuotes=" + inQuotes); + } + i++; + } + pieces.add(line.substring(start, i)); + } + + private void assureEndOfLine() throws BadNodeException { + i++; + while (!atEnd() && isWhiteSpace()) { + i++; + } + if (atEnd()) { + return; + } else if (line.charAt(i) == '#') { + return; + } else { + throw new BadNodeException( + "Period was not followed by end of line: '" + line + "'"); + } + } + + private boolean isQuote() { + boolean isEscaped = i > 0 && line.charAt(i - 1) == '\\'; + return (line.charAt(i) == '"') && (!isEscaped); + } + + public List split() { + return pieces; + } + +} \ No newline at end of file diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NquadsParser.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NquadsParser.java new file mode 100644 index 000000000..77c5c82f2 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NquadsParser.java @@ -0,0 +1,106 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Iterator; +import java.util.List; + +import edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore.DumpNode.BadNodeException; + +/** + * TODO + */ +public class NquadsParser implements DumpParser { + private final BufferedReader r; + + public NquadsParser(InputStream is) throws IOException { + this.r = new BufferedReader(new InputStreamReader(is, "UTF-8")); + } + + @Override + public void close() throws IOException { + r.close(); + } + + @Override + public Iterator iterator() { + return new NQIterator(); + } + + private class NQIterator implements Iterator { + private DumpQuad next = null; + + NQIterator() { + lookAhead(); + } + + private void lookAhead() { + next = null; + String line = null; + try { + while (true) { + line = r.readLine(); + if (line == null) { + return; + } + if (!line.trim().startsWith("#")) { + break; + } + } + } catch (IOException e) { + return; + } + next = parseLine(line); + } + + private DumpQuad parseLine(String line) { + try { + List strings = parseNodeStrings(line); + int stringCount = strings.size(); + if (stringCount != 3 && stringCount != 4) { + throw new BadInputException("Input line is invalid: has " + + stringCount + " groups: '" + line + "' ==> " + + strings); + } + + DumpNode s = DumpNode.fromNquad(strings.get(0)); + DumpNode p = DumpNode.fromNquad(strings.get(1)); + DumpNode o = DumpNode.fromNquad(strings.get(2)); + DumpNode g = DumpNode.fromNquad((stringCount == 4) ? strings + .get(3) : null); + return new DumpQuad(s, p, o, g); + } catch (BadNodeException e) { + throw new BadInputException("unable to parse node, line='" + + line + "'", e); + } + } + + private List parseNodeStrings(String line) + throws BadNodeException { + return new NQuadLineSplitter(line).split(); + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public DumpQuad next() { + DumpQuad dq = next; + lookAhead(); + return dq; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/RestoreModelsAction.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/RestoreModelsAction.java index f75cef781..5ad0b600b 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/RestoreModelsAction.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/RestoreModelsAction.java @@ -3,31 +3,66 @@ package edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore; import static edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore.DumpRestoreController.PARAMETER_FORMAT; +import static edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore.DumpRestoreController.PARAMETER_PURGE; import static edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore.DumpRestoreController.PARAMETER_SOURCE_FILE; +import static edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore.DumpRestoreController.PARAMETER_WHICH; -import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.rdf.model.Model; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore.DumpRestoreController.BadRequestException; import edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore.DumpRestoreController.RestoreFormat; +import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; +import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService.ModelSerializationFormat; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils.WhichService; /** - * TODO - * In progress. + * Load from a dump file of NQuads, or something equivalent. + * + * We could process it all a line at a time, except for the blank nodes. If + * there are two references to the same blank node, they must be processed in + * the same method call to the RDFService. + * + * So, process each line as it comes in, unless it contains a blank node. Lines + * with blank nodes get put into buckets, with one bucket for each model (and + * one for the default model). At the end, we'll empty each of the buckets. + * + * And if they ask to purge the models before restoring, do that. */ public class RestoreModelsAction extends AbstractDumpRestoreAction { + private static final Log log = LogFactory.getLog(RestoreModelsAction.class); + + private static final String DEFAULT_GRAPH_URI = "__default__"; private final FileItem sourceFile; private final RestoreFormat format; + private final WhichService which; + private final boolean purge; + private final TripleBuckets buckets = new TripleBuckets(); RestoreModelsAction(HttpServletRequest req, HttpServletResponse resp) throws BadRequestException { @@ -35,6 +70,9 @@ public class RestoreModelsAction extends AbstractDumpRestoreAction { this.sourceFile = getFileItem(PARAMETER_SOURCE_FILE); this.format = getEnumFromParameter(RestoreFormat.class, PARAMETER_FORMAT); + this.which = getEnumFromParameter(WhichService.class, PARAMETER_WHICH); + this.purge = null != req.getParameter(PARAMETER_PURGE); + ; } private FileItem getFileItem(String key) throws BadRequestException { @@ -46,22 +84,99 @@ public class RestoreModelsAction extends AbstractDumpRestoreAction { return fileItem; } - long restoreModels() throws IOException { + long restoreModels() throws IOException, RDFServiceException { + purgeIfRequested(); + return doTheRestore(); + } + + private void purgeIfRequested() throws RDFServiceException { + if (!purge) { + return; + } + + RDFService rdfService = getRdfService(which); + RDFServiceDataset dataset = new RDFServiceDataset(rdfService); + for (String graphUri : rdfService.getGraphURIs()) { + Model m = dataset.getNamedModel(graphUri); + m.removeAll(); + } + } + + private long doTheRestore() throws IOException, RDFServiceException { long lineCount = 0; try (InputStream is = sourceFile.getInputStream(); - Reader isr = new InputStreamReader(is, "UTF-8"); - BufferedReader br = new BufferedReader(isr)) { - String line; - while (null != (line = br.readLine())) { - processLine(line); + DumpParser p = format.getParser(is)) { + for (DumpQuad line : p) { + bucketize(line); lineCount++; } + emptyBuckets(); } return lineCount; } - private void processLine(String line) { - System.out.println("TOTALLY BOGUS RESTORE"); + private void bucketize(DumpQuad quad) throws IOException, + RDFServiceException { + DumpTriple triple = quad.getTriple(); + if (triple.getS().isBlank() || triple.getO().isBlank()) { + buckets.add(quad.getG().getValue(), triple); + } else { + processTriples(quad.getG().getValue(), + Collections.singleton(triple)); + } } + private void emptyBuckets() throws IOException, RDFServiceException { + for (String key : buckets.getKeys()) { + processTriples(key, buckets.getTriples(key)); + } + } + + private void processTriples(String graphUri, Collection triples) + throws IOException, RDFServiceException { + if (graphUri.equals(DEFAULT_GRAPH_URI)) { + graphUri = null; + } + RDFService rdfService = getRdfService(which); + ChangeSet change = rdfService.manufactureChangeSet(); + change.addAddition(serialize(triples), + ModelSerializationFormat.NTRIPLE, graphUri); + + rdfService.changeSetUpdate(change); + } + + private InputStream serialize(Collection triples) + throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Writer w = new OutputStreamWriter(out, "UTF-8"); + for (DumpTriple triple : triples) { + w.write(triple.toNtriples()); + } + w.close(); + return new ByteArrayInputStream(out.toByteArray()); + } + + private static class TripleBuckets { + private final Map> map = new HashMap<>(); + + public void add(String value, DumpTriple triple) { + String key = (value == null) ? DEFAULT_GRAPH_URI : value; + if (!map.containsKey(key)) { + map.put(key, new ArrayList()); + } + map.get(key).add(triple); + } + + public Set getKeys() { + return map.keySet(); + } + + public List getTriples(String key) { + if (map.containsKey(key)) { + return map.get(key); + } else { + return Collections.emptyList(); + } + } + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraphBulkUpdater.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraphBulkUpdater.java index 320120b45..e2761c2d4 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraphBulkUpdater.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/RDFServiceGraphBulkUpdater.java @@ -29,8 +29,8 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; public class RDFServiceGraphBulkUpdater implements BulkUpdateHandler { + private static final Log log = LogFactory.getLog(RDFServiceGraphBulkUpdater.class); - private static final Log log = LogFactory.getLog(RDFServiceGraphBulkUpdater.class); private final RDFServiceGraph graph; private final GraphEventManager manager; @@ -190,7 +190,8 @@ public class RDFServiceGraphBulkUpdater implements BulkUpdateHandler { } private static void removeAll(Graph g, Node s, Node p, Node o) - { + { + log.debug("removeAll: g=" + g + ", s=" + s + ", p=" + p + ", o=" + o); if (!(g instanceof RDFServiceGraph)) { removeAllTripleByTriple(g, s, p, o); return; diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NQuadLineSplitterTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NQuadLineSplitterTest.java new file mode 100644 index 000000000..62e1e546f --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NQuadLineSplitterTest.java @@ -0,0 +1,62 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.apache.log4j.Level; +import org.junit.BeforeClass; +import org.junit.Test; + +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; +import edu.cornell.mannlib.vitro.webapp.controller.datatools.dumprestore.DumpNode.BadNodeException; + +/** + * TODO + */ +public class NQuadLineSplitterTest extends AbstractTestClass { + private static List testData; + + @BeforeClass + public static void readTestData() throws IOException { + InputStream stream = NQuadLineSplitterTest.class + .getResourceAsStream("NQuadLineSplitterTest.nq"); + testData = IOUtils.readLines(stream); + } + + @Test + public void splitLine1() throws BadNodeException { + List strings = new NQuadLineSplitter(getLine(1)).split(); + assertEquals("count", 4, strings.size()); + assertEquals("subject", "", + strings.get(0)); + assertEquals("predicate", + "", strings.get(1)); + assertEquals( + "object", + "\"The source of the public description and this info is found here: " + + "http://bibotools.googlecode.com/svn/bibo-ontology/trunk/doc/index.html. " + + "Bibo considers this term \\\"unstable\\\". " + + "The bibo editorial note is: \\\"We are not defining, using an enumeration, " + + "the range of the bibo:degree to the defined list of bibo:ThesisDegree. " + + "We won't do it because we want people to be able to define new degress " + + "if needed by some special usecases. " + + "Creating such an enumeration would restrict this to " + + "happen.\\\"\"^^", + strings.get(2)); + assertEquals( + "graph", + "", + strings.get(3)); + } + + private String getLine(int i) { + return testData.get(i - 1); + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NQuadLineSplitterTest.nq b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NQuadLineSplitterTest.nq new file mode 100644 index 000000000..3193ded9b --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/datatools/dumprestore/NQuadLineSplitterTest.nq @@ -0,0 +1 @@ + "The source of the public description and this info is found here: http://bibotools.googlecode.com/svn/bibo-ontology/trunk/doc/index.html. Bibo considers this term \"unstable\". The bibo editorial note is: \"We are not defining, using an enumeration, the range of the bibo:degree to the defined list of bibo:ThesisDegree. We won't do it because we want people to be able to define new degress if needed by some special usecases. Creating such an enumeration would restrict this to happen.\""^^ . diff --git a/webapp/web/templates/freemarker/body/datatools/datatools-dumpRestore.ftl b/webapp/web/templates/freemarker/body/datatools/datatools-dumpRestore.ftl index d63f791d1..fd9142d34 100644 --- a/webapp/web/templates/freemarker/body/datatools/datatools-dumpRestore.ftl +++ b/webapp/web/templates/freemarker/body/datatools/datatools-dumpRestore.ftl @@ -69,7 +69,7 @@ -
+ @@ -98,5 +98,13 @@ + + +
Select models
+ +