/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package fedora.server.validation;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.HashSet;
import org.jrdf.graph.ObjectNode;
import org.jrdf.graph.Triple;
import org.trippi.RDFFormat;
import org.trippi.TripleIterator;
import junit.framework.TestCase;
import fedora.common.Constants;
import fedora.common.PID;
import fedora.common.rdf.SimpleLiteral;
import fedora.common.rdf.SimpleTriple;
import fedora.common.rdf.SimpleURIReference;
import fedora.server.errors.GeneralException;
import fedora.server.errors.ServerException;
import fedora.server.errors.ValidationException;
/**
* Tests the RELS-EXT and RELS-INT datastream deserializer and validation.
*
* @author Edwin Shin
* @author Stephen Bayliss
*/
public class RelsValidatorTest
extends TestCase {
private Collection<Triple> triples;
private static byte[] RELS_EXT;
private static byte[] RELS_INT;
private PID pid;
static {
// create valid RELS-EXT and RELS-INT contents
StringBuilder sbRelsExt = new StringBuilder();
StringBuilder sbRelsInt = new StringBuilder();
sbRelsExt
.append("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\""
+ " xmlns:rel=\"info:fedora/fedora-system:def/relations-external#\">"
+ " <rdf:Description rdf:about=\"info:fedora/demo:888\">"
+ " <rel:isMemberOf rdf:resource=\"info:fedora/demo:X\" />"
+ " </rdf:Description>" + "</rdf:RDF>");
sbRelsInt
.append("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\""
+ " xmlns:relint=\"http://www.example.org/rels-int#\">"
+ " <rdf:Description rdf:about=\"info:fedora/demo:888/DS1\">"
+ " <relint:someRelation rdf:resource=\"info:fedora/demo:X\" />"
+ " </rdf:Description>"
+ " <rdf:Description rdf:about=\"info:fedora/demo:888/DS2\">"
+ " <relint:someProperty>value</relint:someProperty>"
+ " </rdf:Description>"
+ "</rdf:RDF>");
try {
RELS_EXT = sbRelsExt.toString().getBytes("UTF-8");
RELS_INT = sbRelsInt.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
}
}
@Override
public void setUp() {
triples = new HashSet<Triple>();
}
public void testValidateValidRelsExt() throws Exception {
pid = PID.getInstance("demo:888");
InputStream in = new ByteArrayInputStream(RELS_EXT);
RelsValidator validator = new RelsValidator();
validator.validate(pid, "RELS-EXT", in);
}
public void testValidateValidRelsInt() throws Exception {
pid = PID.getInstance("demo:888");
InputStream in = new ByteArrayInputStream(RELS_INT);
RelsValidator validator = new RelsValidator();
validator.validate(pid, "RELS-INT", in);
}
public void testValidateInvalidRelsExt() throws Exception {
pid = PID.getInstance("demo:888");
// use RELS-INT sample as a RELS-EXT datastream
InputStream in = new ByteArrayInputStream(RELS_INT);
RelsValidator validator = new RelsValidator();
try {
validator.validate(pid, "RELS-EXT", in);
fail("Multiple Description elements not allowed for RELS-EXT");
} catch (ValidationException e) {}
}
public void testEmpty() throws Exception {
testEmpty("RELS-EXT");
testEmpty("RELS-INT");
}
private void testEmpty(String dsId) throws Exception {
pid = PID.getInstance("demo:demo");
InputStream in;
String[] empties = {"", " ", "</>"};
for (String s : empties) {
in = new ByteArrayInputStream(s.getBytes());
try {
new RelsValidator().validate(pid, dsId, in);
fail("Empty " + dsId + " datastream incorrectly passed"
+ " validation: \"" + s + "\"");
} catch (ValidationException e) {}
}
}
public void testBadSubjectURI() throws Exception {
pid = PID.getInstance("demo:foo");
String s, p, o;
s = PID.getInstance("demo:bar").toURI();
p = "urn:p";
o = "urn:o";
triples.add(createTriple(s, p, o, false, null));
try {
validateAndClear("RELS-EXT");
fail("RELS-EXT Assertion's subject URI different from digital object URI not allowed");
} catch (ValidationException e) {
}
s = PID.getInstance("demo:foo").toURI();
triples.add(createTriple(s, p, o, false, null));
try {
validateAndClear("RELS-INT");
fail("RELS-INT Assertion's subject URI is digital object URI not allowed");
} catch (ValidationException e) {
}
s = PID.getInstance("demo:bar").toURI() + "/DS1";
triples.add(createTriple(s, p, o, false, null));
try {
validateAndClear("RELS-INT");
fail("RELS-INT Assertion's subject URI is datastream in different digital object not allowed");
} catch (ValidationException e) {
}
s = PID.getInstance("demo:foo").toURI() + "/";
triples.add(createTriple(s, p, o, false, null));
try {
validateAndClear("RELS-INT");
fail("RELS-INT Assertion's subject URI zero length datastream ID not allowed");
} catch (ValidationException e) {
}
s = PID.getInstance("demo:foo") + "/DS1:Test";
triples.add(createTriple(s, p, o, false, null));
try {
validateAndClear("RELS-INT");
fail("RELS-INT Assertion's subject URI invalid datastream ID not allowed (colon)");
} catch (ValidationException e) {
}
}
public void testBadAssertions() throws Exception {
testBadAssertions("RELS-EXT");
testBadAssertions("RELS-INT");
}
// assertions that are invalid for both RELS-EXT and RELS-INT
private void testBadAssertions(String dsId) throws Exception {
pid = PID.getInstance("demo:foo");
String p, o;
// Model namespace
p = Constants.MODEL.CONTROL_GROUP.uri;
o = "demo:baz";
triples.add(createTriple(pid, p, o, false, null));
try {
validateAndClear(dsId);
fail(dsId + "Fedora Model namespace assertions not allowed");
} catch (ValidationException e) {
}
// View namespace
p = Constants.VIEW.DISSEMINATES.uri;
triples.add(createTriple(pid, p, o, false, null));
try {
validateAndClear(dsId);
fail(dsId + "Fedora View namespace assertions not allowed");
} catch (ValidationException e) {
}
}
// RELS-EXT specific assertions (RELS-INT allows these)
public void testAssertionsRelsExt() throws Exception{
pid = PID.getInstance("demo:foo");
String p, o;
p = "http://purl.org/dc/elements/1.1/title";
o = "The God of Small Things";
triples.add(createTriple(pid, p, o, true, null));
try {
validateAndClear("RELS-EXT");
fail("RELS-EXT Dublin Core assertions not allowed");
} catch (ValidationException e) {
}
// specific model relationships are allowed
o = "urn:xyz";
p = Constants.MODEL.HAS_SERVICE.uri;
triples.add(createTriple(pid, p, o, false, null));
try {
validateAndClear("RELS-EXT");
} catch (ValidationException e) {
fail("RELS-EXT Model relationship " + p + " should be allowed");
}
p = Constants.MODEL.IS_CONTRACTOR_OF.uri;
triples.add(createTriple(pid, p, o, false, null));
try {
validateAndClear("RELS-EXT");
} catch (ValidationException e) {
fail("RELS-EXT Model relationship " + p + " should be allowed");
}
p = Constants.MODEL.HAS_MODEL.uri;
triples.add(createTriple(pid, p, o, false, null));
try {
validateAndClear("RELS-EXT");
} catch (ValidationException e) {
fail("RELS-EXT Model relationship " + p + " should be allowed");
}
p = Constants.MODEL.IS_DEPLOYMENT_OF.uri;
triples.add(createTriple(pid, p, o, false, null));
try {
validateAndClear("RELS-EXT");
} catch (ValidationException e) {
fail("RELS-EXT Model relationship " + p + " should be allowed");
}
}
// probably overkill to run these tests for RELS-EXT and RELS-INT, but just in case ...
public void testResourceURI() throws Exception {
testResourceURI("RELS-EXT");
testResourceURI("RELS-INT");
}
private void testResourceURI(String dsId) throws Exception {
pid = PID.getInstance("demo:foo");
String s, p, o;
if (dsId.equals("RELS-INT")) {
s = pid.toURI() + "/DS1";
} else {
s = pid.toURI();
}
p = "urn:p";
o = "urn:o";
triples.add(createTriple(s, p, o, false, null));
p = "urn:p";
o = "urn:o";
triples.add(createTriple(s, p, o, true, null));
p = "urn:p";
o = "1970-01-01T00:00:00Z";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
p = "urn:p";
o = s;
triples.add(createTriple(s, p, o, false, null));
try {
validateAndClear(dsId);
} catch (ValidationException e) {
fail(dsId + " Self-referential assertions should be allowed.");
}
}
public void testDatatypes() throws Exception {
testDatatypes("RELS-EXT");
testDatatypes("RELS-INT");
}
private void testDatatypes(String dsId) throws Exception {
pid = PID.getInstance("demo:foo");
String s, p, o;
if (dsId.equals("RELS-INT")) {
s = pid.toURI() + "/DS1";
} else {
s = pid.toURI();
}
p = "urn:p";
o = "abc:123";
triples.add(createTriple(s, p, o, false, null));
validateAndClear(dsId);
o = "1";
triples.add(createTriple(s, p, o, true, Constants.RDF_XSD.INT.uri));
validateAndClear(dsId);
o = "abc";
triples.add(createTriple(s, p, o, true, Constants.RDF_XSD.INT.uri));
try {
validateAndClear(dsId);
fail("Invalid integer value: " + o);
} catch (ValidationException e) {
}
o = "-0001-01-01T00:00:00";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
o = "1970-01-01T00:00:00";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
o = "1970-01-01T00:00:00.1";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
o = "1970-01-01T00:00:00.01";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
o = "1970-01-01T00:00:00.001";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
o = "-0001-01-01T00:00:00Z";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
o = "1970-01-01T00:00:00Z";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
o = "1970-01-01T00:00:00.1Z";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
o = "1970-01-01T00:00:00.01Z";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
o = "1970-01-01T00:00:00.001Z";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
validateAndClear(dsId);
o = "abc";
triples.add(createTriple(s,
p,
o,
true,
Constants.RDF_XSD.DATE_TIME.uri));
try {
validateAndClear(dsId);
fail("Invalid dateTime value: " + o);
} catch (ValidationException e) {
}
}
private void validateAndClear(String dsId) throws Exception {
try {
TripleIterator iter = new MockTripleIterator(triples);
ByteArrayOutputStream out = new ByteArrayOutputStream();
iter.toStream(out, RDFFormat.RDF_XML, false);
new RelsValidator().validate(pid, dsId, new ByteArrayInputStream(out.toByteArray()));
} finally {
triples.clear();
}
}
private static Triple createTriple(PID pid,
String predicate,
String object,
boolean isLiteral,
String datatype)
throws ServerException {
return createTriple(pid.toURI(), predicate, object, isLiteral, datatype);
}
private static Triple createTriple(String subject,
String predicate,
String object,
boolean isLiteral,
String datatype)
throws ServerException {
ObjectNode o = null;
try {
if (isLiteral) {
if (datatype == null || datatype.length() == 0) {
o = new SimpleLiteral(object);
} else {
o = new SimpleLiteral(object, new URI(datatype));
}
} else {
o = new SimpleURIReference(new URI(object));
}
return new SimpleTriple(new SimpleURIReference(new URI(subject)),
new SimpleURIReference(new URI(predicate)),
o);
} catch (URISyntaxException e) {
throw new GeneralException(e.getMessage(), e);
}
}
}