Java tutorial
package nl.knaw.huygens.alexandria.service; /* * #%L * alexandria-service * ======= * Copyright (C) 2015 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 java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import java.io.IOException; import java.io.OutputStream; import java.time.Duration; import java.time.Instant; import java.time.temporal.TemporalAmount; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; import javax.inject.Inject; import org.apache.commons.lang.NotImplementedException; import com.google.common.collect.Maps; import nl.knaw.huygens.Log; import nl.knaw.huygens.alexandria.endpoint.LocationBuilder; import nl.knaw.huygens.alexandria.endpoint.search.AlexandriaQuery; import nl.knaw.huygens.alexandria.endpoint.search.SearchResult; import nl.knaw.huygens.alexandria.exception.BadRequestException; import nl.knaw.huygens.alexandria.exception.NotFoundException; import nl.knaw.huygens.alexandria.model.Accountable; 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.AlexandriaState; import nl.knaw.huygens.alexandria.model.IdentifiablePointer; import nl.knaw.huygens.alexandria.model.TentativeAlexandriaProvenance; import nl.knaw.huygens.alexandria.query.AlexandriaQueryParser; import nl.knaw.huygens.alexandria.query.ParsedAlexandriaQuery; import nl.knaw.huygens.alexandria.storage.Storage; import nl.knaw.huygens.alexandria.storage.frames.AlexandriaVF; import nl.knaw.huygens.alexandria.storage.frames.AnnotationBodyVF; import nl.knaw.huygens.alexandria.storage.frames.AnnotationVF; import nl.knaw.huygens.alexandria.storage.frames.ResourceVF; public class TinkerPopService implements AlexandriaService { private static final TemporalAmount TENTATIVES_TTL = Duration.ofDays(1); private Storage storage; private LocationBuilder locationBuilder; private AlexandriaQueryParser alexandriaQueryParser; @Inject public TinkerPopService(Storage storage, LocationBuilder locationBuilder) { Log.trace("{} created, locationBuilder=[{}]", getClass().getSimpleName(), locationBuilder); this.locationBuilder = locationBuilder; this.alexandriaQueryParser = new AlexandriaQueryParser(locationBuilder); setStorage(storage); } public void setStorage(Storage storage) { this.storage = storage; } // - AlexandriaService methods -// @Override public boolean createOrUpdateResource(UUID uuid, String ref, TentativeAlexandriaProvenance provenance, AlexandriaState state) { storage.startTransaction(); AlexandriaResource resource; boolean newlyCreated; if (storage.existsVF(ResourceVF.class, uuid)) { resource = readResource(uuid).get(); newlyCreated = false; } else { resource = new AlexandriaResource(uuid, provenance); newlyCreated = true; } resource.setCargo(ref); resource.setState(state); createOrUpdateResource(resource); storage.commitTransaction(); return newlyCreated; } @Override public AlexandriaAnnotation annotate(AlexandriaResource resource, AlexandriaAnnotationBody annotationbody, TentativeAlexandriaProvenance provenance) { AlexandriaAnnotation newAnnotation = createAnnotation(annotationbody, provenance); annotateResourceWithAnnotation(resource, newAnnotation); return newAnnotation; } @Override public AlexandriaAnnotation annotate(AlexandriaAnnotation annotation, AlexandriaAnnotationBody annotationbody, TentativeAlexandriaProvenance provenance) { AlexandriaAnnotation newAnnotation = createAnnotation(annotationbody, provenance); annotateAnnotationWithAnnotation(annotation, newAnnotation); return newAnnotation; } @Override public AlexandriaResource createSubResource(UUID uuid, UUID parentUuid, String sub, TentativeAlexandriaProvenance provenance) { AlexandriaResource subresource = new AlexandriaResource(uuid, provenance); subresource.setCargo(sub); subresource.setParentResourcePointer( new IdentifiablePointer<>(AlexandriaResource.class, parentUuid.toString())); createSubResource(subresource); return subresource; } @Override public Optional<? extends Accountable> dereference(IdentifiablePointer<? extends Accountable> pointer) { Class<? extends Accountable> aClass = pointer.getIdentifiableClass(); UUID uuid = UUID.fromString(pointer.getIdentifier()); if (AlexandriaResource.class.equals(aClass)) { return readResource(uuid); } else if (AlexandriaAnnotation.class.equals(aClass)) { return readAnnotation(uuid); } else { throw new RuntimeException("unexpected accountableClass: " + aClass.getName()); } } @Override public Optional<AlexandriaResource> readResource(UUID uuid) { return storage.readVF(ResourceVF.class, uuid).map(this::deframeResource); } @Override public Optional<AlexandriaAnnotation> readAnnotation(UUID uuid) { return storage.readVF(AnnotationVF.class, uuid).map(this::deframeAnnotation); } @Override public Optional<AlexandriaAnnotation> readAnnotation(UUID uuid, Integer revision) { Optional<AnnotationVF> versionedAnnotation = storage.readVF(AnnotationVF.class, uuid, revision); if (versionedAnnotation.isPresent()) { return versionedAnnotation.map(this::deframeAnnotation); } else { Optional<AnnotationVF> currentAnnotation = storage.readVF(AnnotationVF.class, uuid); if (currentAnnotation.isPresent() && currentAnnotation.get().getRevision().equals(revision)) { return currentAnnotation.map(this::deframeAnnotation); } else { return Optional.empty(); } } } @Override public TemporalAmount getTentativesTimeToLive() { return TENTATIVES_TTL; } @Override public void removeExpiredTentatives() { // Tentative vertices should not have any outgoing or incoming edges!! Long threshold = Instant.now().minus(TENTATIVES_TTL).getEpochSecond(); storage.startTransaction(); storage.removeExpiredTentatives(threshold); storage.commitTransaction(); } @Override public Optional<AlexandriaAnnotationBody> findAnnotationBodyWithTypeAndValue(String type, String value) { final List<AnnotationBodyVF> results = storage.find(AnnotationBodyVF.class)// .has("type", type)// .has("value", value)// .toList(); if (results.isEmpty()) { return Optional.empty(); } return Optional.of(deframeAnnotationBody(results.get(0))); } @Override public Optional<AlexandriaResource> findSubresourceWithSubAndParentId(String sub, UUID parentId) { // TODO: find the gremlin way to do this in one: // in cypher: match (r:Resource{uuid:parentId})<-[:PART_OF]-(s:Resource{cargo:sub}) return s.uuid final List<ResourceVF> results = storage.find(ResourceVF.class)// .has("cargo", sub)// .toList(); if (results.isEmpty()) { return Optional.empty(); } results.stream()// .filter(r -> r.getParentResource() != null// && r.getParentResource().getUuid().equals(parentId.toString()))// .collect(toList()); return Optional.of(deframeResource(results.get(0))); } @Override public Set<AlexandriaResource> readSubResources(UUID uuid) { ResourceVF resourcevf = storage.readVF(ResourceVF.class, uuid)// .orElseThrow(() -> new NotFoundException("no resource found with uuid " + uuid)); return resourcevf.getSubResources().stream()// .map(this::deframeResource)// .collect(toSet()); } @Override public AlexandriaAnnotation deprecateAnnotation(UUID annotationId, AlexandriaAnnotation updatedAnnotation) { storage.startTransaction(); // check if there's an annotation with the given id AnnotationVF oldAnnotationVF = storage.readVF(AnnotationVF.class, annotationId)// .orElseThrow(annotationNotFound(annotationId)); if (oldAnnotationVF.isTentative()) { throw incorrectStateException(annotationId, "tentative"); } else if (oldAnnotationVF.isDeleted()) { throw new BadRequestException("annotation " + annotationId + " is " + "deleted"); } else if (oldAnnotationVF.isDeprecated()) { throw new BadRequestException("annotation " + annotationId + " is " + "already deprecated"); } AlexandriaAnnotationBody newBody = updatedAnnotation.getBody(); Optional<AlexandriaAnnotationBody> optionalBody = findAnnotationBodyWithTypeAndValue(newBody.getType(), newBody.getValue()); AlexandriaAnnotationBody body; if (optionalBody.isPresent()) { body = optionalBody.get(); } else { AnnotationBodyVF annotationBodyVF = frameAnnotationBody(newBody); updateState(annotationBodyVF, AlexandriaState.CONFIRMED); body = newBody; } AlexandriaProvenance tmpProvenance = updatedAnnotation.getProvenance(); TentativeAlexandriaProvenance provenance = new TentativeAlexandriaProvenance(tmpProvenance.getWho(), tmpProvenance.getWhen(), tmpProvenance.getWhy()); AlexandriaAnnotation newAnnotation = new AlexandriaAnnotation(updatedAnnotation.getId(), body, provenance); AnnotationVF newAnnotationVF = frameAnnotation(newAnnotation); AnnotationVF annotatedAnnotation = oldAnnotationVF.getAnnotatedAnnotation(); if (annotatedAnnotation != null) { newAnnotationVF.setAnnotatedAnnotation(annotatedAnnotation); } else { ResourceVF annotatedResource = oldAnnotationVF.getAnnotatedResource(); newAnnotationVF.setAnnotatedResource(annotatedResource); } newAnnotationVF.setDeprecatedAnnotation(oldAnnotationVF); newAnnotationVF.setRevision(oldAnnotationVF.getRevision() + 1); updateState(newAnnotationVF, AlexandriaState.CONFIRMED); oldAnnotationVF.setAnnotatedAnnotation(null); oldAnnotationVF.setAnnotatedResource(null); oldAnnotationVF.setUuid(oldAnnotationVF.getUuid() + "." + oldAnnotationVF.getRevision()); updateState(oldAnnotationVF, AlexandriaState.DEPRECATED); AlexandriaAnnotation resultAnnotation = deframeAnnotation(newAnnotationVF); storage.commitTransaction(); return resultAnnotation; } @Override public void confirmResource(UUID uuid) { storage.startTransaction(); ResourceVF resourceVF = storage.readVF(ResourceVF.class, uuid)// .orElseThrow(resourceNotFound(uuid)); updateState(resourceVF, AlexandriaState.CONFIRMED); storage.commitTransaction(); } @Override public void confirmAnnotation(UUID uuid) { storage.startTransaction(); AnnotationVF annotationVF = storage.readVF(AnnotationVF.class, uuid)// .orElseThrow(annotationNotFound(uuid)); updateState(annotationVF, AlexandriaState.CONFIRMED); updateState(annotationVF.getBody(), AlexandriaState.CONFIRMED); AnnotationVF deprecatedAnnotation = annotationVF.getDeprecatedAnnotation(); if (deprecatedAnnotation != null && !deprecatedAnnotation.isDeprecated()) { updateState(deprecatedAnnotation, AlexandriaState.DEPRECATED); } storage.commitTransaction(); } @Override public void deleteAnnotation(AlexandriaAnnotation annotation) { storage.startTransaction(); UUID uuid = annotation.getId(); AnnotationVF annotationVF = storage.readVF(AnnotationVF.class, uuid).get(); if (annotation.isTentative()) { // remove from database AnnotationBodyVF body = annotationVF.getBody(); List<AnnotationVF> ofAnnotations = body.getOfAnnotationList(); if (ofAnnotations.size() == 1) { String annotationBodyId = body.getUuid(); storage.removeVertexWithId(annotationBodyId); } // remove has_body edge annotationVF.setBody(null); // remove annotates edge annotationVF.setAnnotatedAnnotation(null); annotationVF.setAnnotatedResource(null); String annotationId = uuid.toString(); storage.removeVertexWithId(annotationId); } else { // set state updateState(annotationVF, AlexandriaState.DELETED); } storage.commitTransaction(); } @Override public AlexandriaAnnotationBody createAnnotationBody(UUID uuid, String type, String value, TentativeAlexandriaProvenance provenance) { AlexandriaAnnotationBody body = new AlexandriaAnnotationBody(uuid, type, value, provenance); storeAnnotationBody(body); return body; } @Override public Optional<AlexandriaAnnotationBody> readAnnotationBody(UUID uuid) { throw new NotImplementedException(); } @Override public SearchResult execute(AlexandriaQuery query) { return new SearchResult(locationBuilder)// .setId(UUID.randomUUID())// .setQuery(query)// .setResults(processQuery(query)); } // - other public methods -// public void createSubResource(AlexandriaResource subResource) { storage.startTransaction(); final ResourceVF rvf; final UUID uuid = subResource.getId(); if (storage.existsVF(ResourceVF.class, uuid)) { rvf = storage.readVF(ResourceVF.class, uuid).get(); } else { rvf = storage.createVF(ResourceVF.class); rvf.setUuid(uuid.toString()); } rvf.setCargo(subResource.getCargo()); final UUID parentId = UUID.fromString(subResource.getParentResourcePointer().get().getIdentifier()); Optional<ResourceVF> parentVF = storage.readVF(ResourceVF.class, parentId); rvf.setParentResource(parentVF.get()); setAlexandriaVFProperties(rvf, subResource); storage.commitTransaction(); } public void createOrUpdateAnnotation(AlexandriaAnnotation annotation) { storage.startTransaction(); final AnnotationVF avf; final UUID uuid = annotation.getId(); if (storage.existsVF(AnnotationVF.class, uuid)) { avf = storage.readVF(AnnotationVF.class, uuid).get(); } else { avf = storage.createVF(AnnotationVF.class); avf.setUuid(uuid.toString()); } setAlexandriaVFProperties(avf, annotation); storage.commitTransaction(); } public void annotateResourceWithAnnotation(AlexandriaResource resource, AlexandriaAnnotation newAnnotation) { storage.startTransaction(); AnnotationVF avf = frameAnnotation(newAnnotation); ResourceVF resourceToAnnotate = storage.readVF(ResourceVF.class, resource.getId()).get(); avf.setAnnotatedResource(resourceToAnnotate); storage.commitTransaction(); } public void storeAnnotationBody(AlexandriaAnnotationBody body) { storage.startTransaction(); frameAnnotationBody(body); storage.commitTransaction(); } public void annotateAnnotationWithAnnotation(AlexandriaAnnotation annotation, AlexandriaAnnotation newAnnotation) { storage.startTransaction(); AnnotationVF avf = frameAnnotation(newAnnotation); UUID id = annotation.getId(); annotate(avf, id); storage.commitTransaction(); } public void dumpToGraphSON(OutputStream os) throws IOException { storage.dumpToGraphSON(os); } public void dumpToGraphML(OutputStream os) throws IOException { storage.dumpToGraphML(os); } // - package methods -// void createOrUpdateResource(AlexandriaResource resource) { final ResourceVF rvf; final UUID uuid = resource.getId(); if (storage.existsVF(ResourceVF.class, uuid)) { rvf = storage.readVF(ResourceVF.class, uuid).get(); } else { rvf = storage.createVF(ResourceVF.class); rvf.setUuid(uuid.toString()); } rvf.setCargo(resource.getCargo()); setAlexandriaVFProperties(rvf, resource); } // - private methods -// private AlexandriaAnnotation createAnnotation(AlexandriaAnnotationBody annotationbody, TentativeAlexandriaProvenance provenance) { UUID id = UUID.randomUUID(); return new AlexandriaAnnotation(id, annotationbody, provenance); } AnnotationVF frameAnnotation(AlexandriaAnnotation newAnnotation) { AnnotationVF avf = storage.createVF(AnnotationVF.class); setAlexandriaVFProperties(avf, newAnnotation); avf.setRevision(newAnnotation.getRevision()); UUID bodyId = newAnnotation.getBody().getId(); AnnotationBodyVF bodyVF = storage.readVF(AnnotationBodyVF.class, bodyId).get(); avf.setBody(bodyVF); return avf; } private AlexandriaResource deframeResource(ResourceVF rvf) { TentativeAlexandriaProvenance provenance = deframeProvenance(rvf); UUID uuid = getUUID(rvf); AlexandriaResource resource = new AlexandriaResource(uuid, provenance); resource.setCargo(rvf.getCargo()); resource.setState(AlexandriaState.valueOf(rvf.getState())); resource.setStateSince(Instant.ofEpochSecond(rvf.getStateSince())); for (AnnotationVF annotationVF : rvf.getAnnotatedBy()) { AlexandriaAnnotation annotation = deframeAnnotation(annotationVF); resource.addAnnotation(annotation); } ResourceVF parentResource = rvf.getParentResource(); if (parentResource != null) { resource.setParentResourcePointer( new IdentifiablePointer<>(AlexandriaResource.class, parentResource.getUuid())); } rvf.getSubResources().stream()// .forEach(vf -> resource .addSubResourcePointer(new IdentifiablePointer<>(AlexandriaResource.class, vf.getUuid()))); return resource; } private AlexandriaAnnotation deframeAnnotation(AnnotationVF annotationVF) { TentativeAlexandriaProvenance provenance = deframeProvenance(annotationVF); UUID uuid = getUUID(annotationVF); AlexandriaAnnotationBody body = deframeAnnotationBody(annotationVF.getBody()); AlexandriaAnnotation annotation = new AlexandriaAnnotation(uuid, body, provenance); annotation.setState(AlexandriaState.valueOf(annotationVF.getState())); annotation.setStateSince(Instant.ofEpochSecond(annotationVF.getStateSince())); if (annotationVF.getRevision() == null) { // update old data annotationVF.setRevision(0); } annotation.setRevision(annotationVF.getRevision()); AnnotationVF annotatedAnnotation = annotationVF.getAnnotatedAnnotation(); if (annotatedAnnotation == null) { ResourceVF annotatedResource = annotationVF.getAnnotatedResource(); if (annotatedResource != null) { annotation.setAnnotatablePointer( new IdentifiablePointer<>(AlexandriaResource.class, annotatedResource.getUuid())); } } else { annotation.setAnnotatablePointer( new IdentifiablePointer<>(AlexandriaAnnotation.class, annotatedAnnotation.getUuid())); } for (AnnotationVF avf : annotationVF.getAnnotatedBy()) { AlexandriaAnnotation annotationAnnotation = deframeAnnotation(avf); annotation.addAnnotation(annotationAnnotation); } return annotation; } private AnnotationBodyVF frameAnnotationBody(AlexandriaAnnotationBody body) { AnnotationBodyVF abvf = storage.createVF(AnnotationBodyVF.class); setAlexandriaVFProperties(abvf, body); abvf.setType(body.getType()); abvf.setValue(body.getValue()); return abvf; } private AlexandriaAnnotationBody deframeAnnotationBody(AnnotationBodyVF annotationBodyVF) { TentativeAlexandriaProvenance provenance = deframeProvenance(annotationBodyVF); UUID uuid = getUUID(annotationBodyVF); return new AlexandriaAnnotationBody(uuid, annotationBodyVF.getType(), annotationBodyVF.getValue(), provenance); } private TentativeAlexandriaProvenance deframeProvenance(AlexandriaVF avf) { String provenanceWhen = avf.getProvenanceWhen(); return new TentativeAlexandriaProvenance(avf.getProvenanceWho(), Instant.parse(provenanceWhen), avf.getProvenanceWhy()); } private void setAlexandriaVFProperties(AlexandriaVF vf, Accountable accountable) { vf.setUuid(accountable.getId().toString()); vf.setState(accountable.getState().toString()); vf.setStateSince(accountable.getStateSince().getEpochSecond()); AlexandriaProvenance provenance = accountable.getProvenance(); vf.setProvenanceWhen(provenance.getWhen().toString()); vf.setProvenanceWho(provenance.getWho()); vf.setProvenanceWhy(provenance.getWhy()); } // framedGraph methods private Supplier<NotFoundException> annotationNotFound(UUID id) { return () -> new NotFoundException("no annotation found with uuid " + id); } private Supplier<NotFoundException> resourceNotFound(UUID id) { return () -> new NotFoundException("no resource found with uuid " + id); } private BadRequestException incorrectStateException(UUID oldAnnotationId, String string) { return new BadRequestException("annotation " + oldAnnotationId + " is " + string); } private UUID getUUID(AlexandriaVF vf) { return UUID.fromString(vf.getUuid().replaceFirst("\\..$", "")); // remove revision suffix for deprecated annotations } void updateState(AlexandriaVF vf, AlexandriaState newState) { vf.setState(newState.name()); vf.setStateSince(Instant.now().getEpochSecond()); } void annotate(AnnotationVF avf, UUID id) { AnnotationVF annotationToAnnotate = storage.readVF(AnnotationVF.class, id).get(); avf.setAnnotatedAnnotation(annotationToAnnotate); } private List<Map<String, Object>> processQuery(AlexandriaQuery query) { ParsedAlexandriaQuery pQuery = alexandriaQueryParser.parse(query); Predicate<AnnotationVF> predicate = pQuery.getPredicate(); Comparator<AnnotationVF> comparator = pQuery.getResultComparator(); Function<AnnotationVF, Map<String, Object>> mapper = pQuery.getResultMapper(); Stream<AnnotationVF> stream = pQuery.getAnnotationVFFinder().apply(storage); Log.debug("list={}", stream); List<Map<String, Object>> results = stream// .filter(predicate)// .sorted(comparator)// .map(mapper)// .collect(toList()); Log.debug("results={}", results); return results; } // @SuppressWarnings("unchecked") // private void findAllConfirmedAnnotationsRelatedToResource(String uuid) { // // case: find all annotations related to a given resource (or its subresources) // // start with the resource // // from there: find subresources, add them to resource list // // foreach resource in the list, get the confirmed annotations of that resource, add them to annotations list // // foreach annotation in the annotationlist, find the confirmed annotations of that annotation, add them to annotations list // GraphTraversal<Vertex, Vertex> traversal = storage.getVertexTraversal(); // traversal.has("uuid", uuid).in(ResourceVF.PART_OF).in("annotates").has("state", "CONFIRMED").as("a").out("has_body").as("b").toList(); // traversal.has("uuid", uuid) // .union(// // __.in(ResourceVF.PART_OF).in(AnnotationVF.ANNOTATES_RESOURCE), // // __.in(AnnotationVF.ANNOTATES_RESOURCE)// // ).has("state", AlexandriaState.CONFIRMED.name()).as("a").out(AnnotationVF.HAS_BODY).as("b").toList(); // } @Override public Map<String, Object> getMetadata() { Map<String, Object> metadata = Maps.newLinkedHashMap(); metadata.put("type", this.getClass().getCanonicalName()); metadata.put("storage", storage.getMetadata()); return metadata; } @Override public void destroy() { // Log.info("destroy called"); storage.destroy(); // Log.info("destroy done"); } }