package nl.knaw.huygens.alexandria.service;
/*
* #%L
* alexandria-service
* =======
* Copyright (C) 2015 - 2017 Huygens ING (KNAW)
* =======
* This program 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.
*
* This program 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 this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.StreamingOutput;
import org.apache.tinkerpop.gremlin.structure.Element;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONIo;
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
import org.junit.Ignore;
import org.junit.Test;
import com.google.common.collect.ImmutableMap;
import jersey.repackaged.com.google.common.collect.Lists;
import nl.knaw.huygens.Log;
import nl.knaw.huygens.alexandria.api.model.AlexandriaState;
import nl.knaw.huygens.alexandria.api.model.CommandResponse;
import nl.knaw.huygens.alexandria.api.model.text.view.ElementDefinition;
import nl.knaw.huygens.alexandria.api.model.text.view.TextView;
import nl.knaw.huygens.alexandria.api.model.text.view.TextViewDefinition;
import nl.knaw.huygens.alexandria.config.MockConfiguration;
import nl.knaw.huygens.alexandria.endpoint.EndpointPathResolver;
import nl.knaw.huygens.alexandria.endpoint.LocationBuilder;
import nl.knaw.huygens.alexandria.endpoint.command.WrapContentInElementCommand;
import nl.knaw.huygens.alexandria.model.AlexandriaAnnotation;
import nl.knaw.huygens.alexandria.model.AlexandriaAnnotationBody;
import nl.knaw.huygens.alexandria.model.AlexandriaProvenance;
import nl.knaw.huygens.alexandria.model.AlexandriaResource;
import nl.knaw.huygens.alexandria.model.TentativeAlexandriaProvenance;
import nl.knaw.huygens.alexandria.test.AlexandriaTest;
import nl.knaw.huygens.alexandria.textgraph.ParseResult;
import nl.knaw.huygens.alexandria.textgraph.TextGraphUtil;
public class TinkerPopServiceTest extends AlexandriaTest {
TinkerPopService service = new TinkerGraphService(new LocationBuilder(new MockConfiguration(), new EndpointPathResolver()));
@Test
public void testReadAfterCreateIsIdentity() {
UUID id = UUID.randomUUID();
String who = "who";
Instant when = Instant.now();
String why = "why";
TentativeAlexandriaProvenance provenance = new TentativeAlexandriaProvenance(who, when, why);
AlexandriaResource resource = new AlexandriaResource(id, provenance);
resource.setCargo("reference");
// logGraphAsJson();
Log.info("resource={}", resource);
service.createOrUpdateResource(resource);
// logGraphAsJson();
Log.info("after createOrUpdate");
AlexandriaResource read = service.readResource(id).get();
Log.info("after read");
// logGraphAsJson();
Log.info("read={}", read);
assertThat(read).isEqualToComparingOnlyGivenFields(resource, "cargo", "id", "state");
assertThat(read.getProvenance()).isEqualToComparingOnlyGivenFields(resource.getProvenance(), "who", "why", "when");
}
// private void logGraphAsJson() {
// try (final ByteArrayOutputStream os = new ByteArrayOutputStream()) {
// g.io(new GraphSONIo.Builder()).writer().create().writeGraph(os, g);
// Log.info("graph as json={}", new String(os.toByteArray(), StandardCharsets.UTF_8));
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// }
TinkerGraph graph = TinkerGraph.open();
// class TestStorage extends TinkerPopService {
// public TestStorage() {
// super(graph);
// }
// }
@Ignore
@Test
public void testDeleteTentativeAnnotationWithUniqueBodyRemovesAnnotationAndBody() {
// TODO
// TinkerPopService s = new TestStorage();
AlexandriaAnnotation annotation = mock(AlexandriaAnnotation.class);
service.deleteAnnotation(annotation);
}
@Ignore
@Test
public void testDeleteTentativeAnnotationWithSharedBodyRemovesAnnotationAndLeavesBody() {
// TODO
AlexandriaAnnotation annotation = mock(AlexandriaAnnotation.class);
service.deleteAnnotation(annotation);
}
@Ignore
@Test
public void testDeleteConfirmedAnnotationSetsStateToDeleted() {
// TODO
AlexandriaAnnotation annotation = mock(AlexandriaAnnotation.class);
service.deleteAnnotation(annotation);
}
@Test
public void testGraphDrop() throws IOException {
TinkerGraph graph = TinkerGraph.open();
Vertex a1 = graph.addVertex("A");
a1.property("key", "value");
Vertex a2 = graph.addVertex("A");
Vertex b1 = graph.addVertex("B");
b1.addEdge("knows", a1, "property1", "value1");
logGraph(graph);
graph.traversal().V().hasLabel("A").forEachRemaining(Element::remove);
logGraph(graph);
}
@Test
public void testUuid() {
UUID u = UUID.fromString("11111111-1111-1111-1111-111111111111");
assertThat(u).isNotNull();
}
@Test
public void testDeprecateAnnotation() {
AlexandriaResource resource = aResource();
service.createOrUpdateResource(resource);
UUID annotationBodyId = UUID.randomUUID();
TentativeAlexandriaProvenance provenance1 = new TentativeAlexandriaProvenance("who1", Instant.now(), "why1");
AlexandriaAnnotationBody body1 = service.createAnnotationBody(annotationBodyId, "type", "value", provenance1);
UUID annotationId = UUID.randomUUID();
TentativeAlexandriaProvenance provenance2 = new TentativeAlexandriaProvenance("who2", Instant.now(), "why2");
AlexandriaAnnotation annotation = new AlexandriaAnnotation(annotationId, body1, provenance2);
service.annotateResourceWithAnnotation(resource, annotation);
service.confirmAnnotation(annotationId);
assertThat(annotation.getRevision()).isEqualTo(0);
UUID annotationBodyId2 = UUID.randomUUID();
TentativeAlexandriaProvenance provenance3 = new TentativeAlexandriaProvenance("who3", Instant.now(), "why3");
AlexandriaAnnotationBody body2 = service.createAnnotationBody(annotationBodyId2, "type", "updated value", provenance3);
TentativeAlexandriaProvenance provenance4 = new TentativeAlexandriaProvenance("who4", Instant.now(), "why4");
AlexandriaAnnotation updatedAnnotation = new AlexandriaAnnotation(annotationId, body2, provenance4);
// when
AlexandriaAnnotation newAnnotation = service.deprecateAnnotation(annotationId, updatedAnnotation);
// then expect
assertThat(newAnnotation.getId()).isEqualTo(annotationId);
assertThat(newAnnotation.getState()).isEqualTo(AlexandriaState.CONFIRMED);
assertThat(newAnnotation.getBody().getType()).isEqualTo("type");
assertThat(newAnnotation.getBody().getValue()).isEqualTo("updated value");
assertThat(newAnnotation.getAnnotatablePointer().getIdentifier()).isEqualTo(resource.getId().toString());
assertThat(newAnnotation.getProvenance().getWhen()).isEqualTo(provenance4.getWhen());
assertThat(newAnnotation.getProvenance().getWho()).isEqualTo(provenance4.getWho());
assertThat(newAnnotation.getProvenance().getWhy()).isEqualTo(provenance4.getWhy());
assertThat(newAnnotation.getRevision()).isEqualTo(1);
}
private void logGraph(TinkerGraph graph) throws IOException {
OutputStream os = new ByteArrayOutputStream();
graph.io(GraphSONIo.build()).writer().create().writeGraph(os, graph);
Log.info("graph={}", os.toString());
}
// @Ignore
// @Test
// public void testTraversal() {
// GraphTraversal<Vertex, Vertex> traversal = graph.traversal().V();
// traversal = traversal.and(hasLabel("Annotation"), hasLabel("Resource"))//
// .values("uuid");
// Map<String, Object> propertyMap = traversal.propertyMap().next();
// }
@Test
public void testReturnExistingSubresourceIfSubPlusParentIdMatches() {
AlexandriaResource resource = aResource();
service.createOrUpdateResource(resource);
UUID resourceId = resource.getId();
TentativeAlexandriaProvenance provenance = copyOf(resource.getProvenance());
UUID subUuid = UUID.randomUUID();
String sub = "sub";
service.createSubResource(subUuid, resourceId, sub, provenance);
Optional<AlexandriaResource> oResource = service.findSubresourceWithSubAndParentId(sub, resourceId);
assertThat(oResource).isPresent();
assertThat(oResource.get().getId()).isEqualTo(subUuid);
// now, create a new resource
UUID resourceId1 = UUID.randomUUID();
AlexandriaResource resource1 = new AlexandriaResource(resourceId1, provenance);
service.createOrUpdateResource(resource1);
// I should be able to make a subresource on this new resource with the same value for sub
Optional<AlexandriaResource> oResource1 = service.findSubresourceWithSubAndParentId(sub, resourceId1);
assertThat(oResource1.isPresent()).isFalse();
}
@Test
public void testDeletingAnAnnotationWithStateDeletedDoesNotFail() {
AlexandriaResource resource = aResource();
service.createOrUpdateResource(resource);
UUID annotationBodyId = UUID.randomUUID();
TentativeAlexandriaProvenance provenance1 = new TentativeAlexandriaProvenance("who1", Instant.now(), "why1");
AlexandriaAnnotationBody body1 = service.createAnnotationBody(annotationBodyId, "type", "value", provenance1);
UUID annotationId = UUID.randomUUID();
TentativeAlexandriaProvenance provenance2 = new TentativeAlexandriaProvenance("who2", Instant.now(), "why2");
AlexandriaAnnotation annotation = new AlexandriaAnnotation(annotationId, body1, provenance2);
service.annotateResourceWithAnnotation(resource, annotation);
annotation = service.readAnnotation(annotationId).get();
assertThat(annotation.getState()).isEqualTo(AlexandriaState.TENTATIVE);
service.confirmAnnotation(annotationId);
annotation = service.readAnnotation(annotationId).get();
assertThat(annotation.getState()).isEqualTo(AlexandriaState.CONFIRMED);
service.deleteAnnotation(annotation);
annotation = service.readAnnotation(annotationId).get();
assertThat(annotation.getState()).isEqualTo(AlexandriaState.DELETED);
service.deleteAnnotation(annotation);
annotation = service.readAnnotation(annotationId).get();
assertThat(annotation.getState()).isEqualTo(AlexandriaState.DELETED);
}
@Test
public void testGetTextViewDefinitionsForResourceReturnsTheFirstDefinitionUpTheResourceChain() {
AlexandriaResource resource = aResource();
service.createOrUpdateResource(resource);
ElementDefinition bedText = ElementDefinition.withName("text");
ElementDefinition bedDiv = ElementDefinition.withName("div");
List<ElementDefinition> list = Lists.newArrayList(bedText, bedDiv);
TextView textView = new TextView();
UUID resourceId = resource.getId();
TextViewDefinition textViewDefinition = new TextViewDefinition();
service.setTextView(resourceId, "baselayer", textView, textViewDefinition);
UUID subUuid1 = UUID.randomUUID();
String sub = "sub1";
TentativeAlexandriaProvenance provenance = copyOf(resource.getProvenance());
service.createSubResource(subUuid1, resourceId, sub, provenance);
UUID subUuid2 = UUID.randomUUID();
service.createSubResource(subUuid2, subUuid1, "sub2", provenance);
List<TextView> views = service.getTextViewsForResource(subUuid2);
assertThat(views).isNotEmpty();
// List<ElementDefinition> returnedElementDefinitions = views.get(0).getIncludedElementDefinitions();
// assertThat(returnedElementDefinitions).containsExactly(bedText, bedDiv);
}
private TentativeAlexandriaProvenance copyOf(AlexandriaProvenance provenance) {
return new TentativeAlexandriaProvenance(provenance.getWho(), provenance.getWhen(), provenance.getWhy());
}
@Test
public void testGetTextViewDefinitionsForResourceReturnsNullOptionalsWhenNoDefinitionPresentUpTheResourceChain() {
AlexandriaResource resource = aResource();
service.createOrUpdateResource(resource);
UUID resourceId = resource.getId();
TentativeAlexandriaProvenance provenance = copyOf(resource.getProvenance());
UUID subUuid1 = UUID.randomUUID();
String sub = "sub1";
service.createSubResource(subUuid1, resourceId, sub, provenance);
UUID subUuid2 = UUID.randomUUID();
service.createSubResource(subUuid2, subUuid1, "sub2", provenance);
List<TextView> textViews = service.getTextViewsForResource(subUuid2);
assertThat(textViews).isEmpty();
}
@Test
public void testXmlInEqualsXmlOut() throws WebApplicationException, IOException {
// given
String xml = singleQuotesToDouble("<xml><p xml:id='p-1' a='A' z='Z' b='B'>Bla</p></xml>");
UUID resourceId = aResourceUUIDWithXml(xml);
// when
String out = getResourceXml(resourceId);
// then
assertThat(out).isEqualTo(xml);
}
@Test
public void testMilestoneHandling() throws WebApplicationException, IOException {
// given
String xml = singleQuotesToDouble("<text>\n"//
+ "<pb n='1' xml:id='pb-1'></pb>\n"//
+ "<p><figure><graphic url='beec002jour04ill02.gif'/></figure></p>\n"//
+ "</text>");
String expectedXml = singleQuotesToDouble("<text>\n"//
+ "<pb n='1' xml:id='pb-1'/>\n"//
+ "<p><figure><graphic url='beec002jour04ill02.gif'/></figure></p>\n"//
+ "</text>");
UUID resourceId = aResourceUUIDWithXml(xml);
// when
String out = getResourceXml(resourceId);
// then
assertThat(out).isEqualTo(expectedXml);
}
@Test
public void testWrapContentInElementWorks() throws WebApplicationException, IOException {
// given
String xml = singleQuotesToDouble("<text>"//
+ "<div xml:id='div-1'><p xml:id='p-1'>Paragraph the First.</p></div>"//
+ "<div xml:id='div-2'><p xml:id='p-2'>Paragraph the Second.</p></div>"//
+ "</text>");
String expected = singleQuotesToDouble("<text>"//
+ "<div xml:id='div-1'><hi rend='blue'><p xml:id='p-1'>Paragraph the First.</p></hi></div>"//
+ "<div xml:id='div-2'><p xml:id='p-2'><hi rend='blue'>Paragraph the Second.</hi></p></div>"//
+ "</text>");
UUID resourceId = aResourceUUIDWithXml(xml);
WrapContentInElementCommand command = new WrapContentInElementCommand(service);
ImmutableMap<String, Serializable> elementMap = ImmutableMap.of(//
"name", "hi", //
"attributes", ImmutableMap.of("rend", "blue")//
);
Map<String, Object> parameterMap = ImmutableMap.<String, Object> builder()//
.put("resourceIds", Lists.newArrayList(resourceId.toString()))//
.put("xmlIds", Lists.newArrayList("div-1", "p-2"))//
.put("element", elementMap)//
.build();
// when
CommandResponse response = command.runWith(parameterMap);
// then
assertThat(response.getErrorLines()).isEmpty();
assertThat(response.parametersAreValid()).isTrue();
String out = getResourceXml(resourceId);
assertThat(out).isEqualTo(expected);
}
private AlexandriaResource aResource() {
UUID resourceId = UUID.randomUUID();
TentativeAlexandriaProvenance provenance = new TentativeAlexandriaProvenance("who", Instant.now(), "why");
return new AlexandriaResource(resourceId, provenance);
}
private UUID aResourceUUIDWithXml(String xml) {
AlexandriaResource resource = aResource();
service.createOrUpdateResource(resource);
UUID resourceId = resource.getId();
ParseResult result = TextGraphUtil.parse(xml);
service.storeTextGraph(resourceId, result);
return resourceId;
}
private String getResourceXml(UUID resourceId) throws IOException {
StreamingOutput streamXML = TextGraphUtil.streamXML(service, resourceId);
OutputStream output = new ByteArrayOutputStream();
streamXML.write(output);
output.flush();
return output.toString();
}
}