package nl.knaw.huygens.alexandria.endpoint.webannotation;
/*
* #%L
* alexandria-main
* =======
* 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 nl.knaw.huygens.alexandria.api.w3c.WebAnnotationConstants.OA_HAS_SOURCE_IRI;
import static nl.knaw.huygens.alexandria.api.w3c.WebAnnotationConstants.OA_HAS_TARGET_IRI;
import static nl.knaw.huygens.alexandria.api.w3c.WebAnnotationConstants.WEBANNOTATION_TYPE;
import java.io.IOException;
import java.net.URI;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.ws.rs.core.UriBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.github.jsonldjava.core.JsonLdError;
import com.github.jsonldjava.core.JsonLdOptions;
import com.github.jsonldjava.core.JsonLdProcessor;
import com.github.jsonldjava.utils.JsonUtils;
import nl.knaw.huygens.alexandria.api.EndpointPaths;
import nl.knaw.huygens.alexandria.api.model.AlexandriaState;
import nl.knaw.huygens.alexandria.api.model.w3c.WebAnnotationPrototype;
import nl.knaw.huygens.alexandria.config.AlexandriaConfiguration;
import nl.knaw.huygens.alexandria.exception.BadRequestException;
import nl.knaw.huygens.alexandria.jaxrs.ThreadContext;
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.service.AlexandriaService;
public class WebAnnotationService {
private static final Logger LOG = LoggerFactory.getLogger(WebAnnotationService.class);
private final AlexandriaService service;
private final AlexandriaConfiguration config;
private final AlexandriaResourceCache cache = new AlexandriaResourceCache();
public WebAnnotationService(AlexandriaService service, AlexandriaConfiguration config) {
this.service = service;
this.config = config;
}
public ObjectNode validateAndStore(ObjectNode annotationNode) {
try {
String json = new ObjectMapper().writeValueAsString(annotationNode);
String resourceRef = extractResourceRef(annotationNode);
AlexandriaResource alexandriaResource = extractAlexandriaResource(resourceRef);
AlexandriaAnnotation annotation = createWebAnnotation(json, alexandriaResource);
annotationNode.set("@id", new TextNode(webAnnotationURI(annotation.getId()).toString()));
return annotationNode;
} catch (IOException | JsonLdError e) {
throw new BadRequestException(e.getMessage());
}
}
public WebAnnotation validateAndStore(WebAnnotationPrototype prototype) {
try {
String json = new ObjectMapper().writeValueAsString(prototype);
Map<String, Object> jsonObject = (Map<String, Object>) JsonUtils.fromString(json);
String resourceRef = extractResourceRef(jsonObject);
AlexandriaResource alexandriaResource = extractAlexandriaResource(resourceRef);
AlexandriaAnnotation annotation = createWebAnnotation(json, alexandriaResource);
jsonObject.put("@id", webAnnotationURI(annotation.getId()));
String json2 = new ObjectMapper().writeValueAsString(jsonObject);
return new WebAnnotation(annotation.getId())//
.setJson(json2)//
.setETag(String.valueOf(prototype.getModified().hashCode()));
} catch (IOException | JsonLdError e) {
throw new BadRequestException(e.getMessage());
}
}
URI webAnnotationURI(UUID annotationUUID) {
return UriBuilder.fromUri(config.getBaseURI())//
.path(EndpointPaths.WEB_ANNOTATIONS)//
.path(annotationUUID.toString())//
.build();
}
private String extractResourceRef(Object annotationObject) throws JsonLdError {
try {
String json = new ObjectMapper().writeValueAsString(annotationObject);
Object jsonObject = JsonUtils.fromString(json);
JsonLdOptions options = new JsonLdOptions();
// LOG.info("annotationObject={}", annotationObject);
Map<Object, Object> context = new HashMap<>();
Map<String, Object> compacted = JsonLdProcessor.compact(jsonObject, context, options);
// LOG.info("compacted={}", compacted);
Map<String, Object> target = (Map<String, Object>) compacted.get(OA_HAS_TARGET_IRI);
String resourceRef = "";
if (target == null) {
LOG.error("target==null!, compacted={}", compacted); // TODO!
} else {
if (target.containsKey("@id")) {
resourceRef = (String) target.get("@id");
} else if (target.containsKey(OA_HAS_SOURCE_IRI)) {
Map<String, Object> source = (Map<String, Object>) target.get(OA_HAS_SOURCE_IRI);
resourceRef = (String) source.get("@id");
}
}
return resourceRef.replaceFirst("#.*", "");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private AlexandriaResource extractAlexandriaResource(String resourceRef) {
// first see if there's already a resource with this ref, if so, use this.
return cache.get(resourceRef).orElseGet(() -> {
AlexandriaResource resourceWithRef = service.readResourceWithUniqueRef(resourceRef).orElseGet(() -> {
UUID resourceUUID = UUID.randomUUID();
TentativeAlexandriaProvenance provenance = new TentativeAlexandriaProvenance(ThreadContext.getUserName(), Instant.now(), AlexandriaProvenance.DEFAULT_WHY);
return service.createResource(resourceUUID, resourceRef, provenance, AlexandriaState.CONFIRMED);
});
cache.add(resourceWithRef);
return resourceWithRef;
});
}
private AlexandriaAnnotation createWebAnnotation(String json, AlexandriaResource alexandriaResource) {
UUID annotationBodyUUID = UUID.randomUUID();
// TODO: use information from prototype to create provenance
TentativeAlexandriaProvenance annotationProvenance = new TentativeAlexandriaProvenance(ThreadContext.getUserName(), Instant.now(), AlexandriaProvenance.DEFAULT_WHY);
AlexandriaAnnotationBody annotationBody = service.createAnnotationBody(annotationBodyUUID, WEBANNOTATION_TYPE, json, annotationProvenance);
AlexandriaAnnotation annotation = service.annotate(alexandriaResource, annotationBody, annotationProvenance);
service.confirmAnnotation(annotation.getId());
return annotation;
}
}