package nl.knaw.huygens.alexandria.endpoint.resource; /* * #%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 java.util.stream.Collectors.toList; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutorService; import javax.inject.Inject; import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.UriInfo; import org.apache.commons.io.Charsets; import org.apache.commons.io.IOUtils; import io.swagger.annotations.ApiOperation; import nl.knaw.huygens.alexandria.api.EndpointPaths; import nl.knaw.huygens.alexandria.api.model.ProcessStatusMap; import nl.knaw.huygens.alexandria.api.model.text.TextAnnotationImportStatus; import nl.knaw.huygens.alexandria.api.model.text.TextEntity; import nl.knaw.huygens.alexandria.api.model.text.TextImportStatus; import nl.knaw.huygens.alexandria.api.model.text.TextRangeAnnotationList; import nl.knaw.huygens.alexandria.api.model.text.view.TextViewEntity; import nl.knaw.huygens.alexandria.config.AlexandriaConfiguration; import nl.knaw.huygens.alexandria.endpoint.JSONEndpoint; import nl.knaw.huygens.alexandria.endpoint.LocationBuilder; import nl.knaw.huygens.alexandria.endpoint.ResourceTextFactory; import nl.knaw.huygens.alexandria.endpoint.UUIDParam; import nl.knaw.huygens.alexandria.exception.ConflictException; import nl.knaw.huygens.alexandria.exception.NotFoundException; import nl.knaw.huygens.alexandria.model.AlexandriaResource; import nl.knaw.huygens.alexandria.service.AlexandriaService; import nl.knaw.huygens.alexandria.text.TextPrototype; import nl.knaw.huygens.alexandria.textgraph.DotFactory; import nl.knaw.huygens.alexandria.textgraph.TextGraphImportTask; import nl.knaw.huygens.alexandria.textgraph.TextGraphUtil; import nl.knaw.huygens.alexandria.textgraph.TextRangeAnnotationValidatorFactory; public class ResourceTextEndpoint extends JSONEndpoint { private final AlexandriaService service; private final UUID resourceUUID; private final AlexandriaResource resource; private ExecutorService executorService; private final LocationBuilder locationBuilder; private final ProcessStatusMap<TextImportStatus> taskStatusMap; private final ProcessStatusMap<TextAnnotationImportStatus> annotationTaskStatusMap; private AlexandriaConfiguration config; private ResourceTextFactory textFactory; private TextRangeAnnotationValidatorFactory textRangeAnnotationValidator; @Inject public ResourceTextEndpoint(AlexandriaService service, // AlexandriaConfiguration config, // ResourceValidatorFactory validatorFactory, // ExecutorService executorService, // LocationBuilder locationBuilder, // ResourceTextFactory resourceTextFactory, // ProcessStatusMap<TextImportStatus> taskStatusMap, // ProcessStatusMap<TextAnnotationImportStatus> annotationTaskStatusMap, // @PathParam("uuid") final UUIDParam uuidParam) { this.service = service; this.config = config; this.executorService = executorService; this.locationBuilder = locationBuilder; this.textFactory = resourceTextFactory; this.taskStatusMap = taskStatusMap; this.annotationTaskStatusMap = annotationTaskStatusMap; this.resource = validatorFactory.validateExistingResource(uuidParam).notTentative().get(); this.resourceUUID = resource.getId(); this.textRangeAnnotationValidator = new TextRangeAnnotationValidatorFactory(service, resourceUUID); } @PUT @Consumes(MediaType.TEXT_XML) @ApiOperation("set text from xml") public Response setTextFromXml(@NotNull @Valid String xml) { assertResourceHasNoText(); startTextProcessing(xml); return Response.accepted()// .location(locationBuilder.locationOf(resource, EndpointPaths.TEXT, "status"))// .build(); } @PUT @Consumes(MediaType.APPLICATION_JSON) @ApiOperation("set text from text prototype") public Response setTextWithPrototype(@NotNull @Valid TextPrototype prototype) { String body = prototype.getBody(); return setTextFromXml(body); } @PUT @Consumes(MediaType.APPLICATION_OCTET_STREAM) @ApiOperation("set text from stream") public Response setTextFromXmlStream(InputStream inputStream) { try { String xml = IOUtils.toString(inputStream, Charsets.UTF_8); return setTextFromXml(xml); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } } @GET public Response getTextEntity() { assertResourceHasText(); List<TextViewEntity> textViewEntities = service.getTextViewsForResource(resourceUUID)// .stream()// .map(tv -> textFactory.createTextViewEntity(resourceUUID, tv))// .collect(toList()); TextEntity text = textFactory.createTextEntity(resourceUUID, textViewEntities); return ok(text); } @Path(EndpointPaths.TEXTVIEWS) public Class<ResourceTextViewEndpoint> getTextViewEndpoint() { return ResourceTextViewEndpoint.class; // no instantiation of our own; let Jersey handle the lifecycle } @GET @Path("status") public Response getTextGraphImportStatus() { TextImportStatus textGraphImportTaskStatus = taskStatusMap.get(resourceUUID)// .orElseThrow(NotFoundException::new); return ok(textGraphImportTaskStatus); } @GET @Path("xml") @Produces(MediaType.TEXT_XML) @ApiOperation("get textgraph as xml") public Response getXML(@QueryParam("view") String viewName, @Context UriInfo uriInfo) { assertResourceHasText(); Map<String, String> viewParameters = getViewParameters(uriInfo); StreamingOutput outputstream = TextGraphUtil.xmlOutputStream(service, resourceUUID, viewName, viewParameters); return ok(outputstream); } static Map<String, String> getViewParameters(UriInfo uriInfo) { MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters(); Map<String, String> viewParameters = new HashMap<>(); queryParameters.forEach((k, v) -> { if (v.size() > 1) { throw new BadRequestException("view parameter '" + k + "' has multiple values, should be single-valued"); } if (k.startsWith("view.")) { viewParameters.put(k.replace("view.", ""), v.get(0)); } }); return viewParameters; } @GET @Path("dot") @Produces(MediaType.TEXT_PLAIN + "; charSet=utf-8") @ApiOperation("get textgraph as .dot output") public Response getDot() { assertResourceHasText(); String dot = DotFactory.createDot(service, resourceUUID); return ok(dot); } @Path(EndpointPaths.ANNOTATIONS) public Class<ResourceTextAnnotationEndpoint> getResourceTextAnnotationEndpoint(@PathParam("uuid") final UUIDParam uuidParam) { return ResourceTextAnnotationEndpoint.class; // no instantiation of our own; let Jersey handle the lifecycle } @POST @Path(EndpointPaths.ANNOTATIONBATCH) @Consumes(MediaType.APPLICATION_JSON) public Response postAnnotationList(@NotNull TextRangeAnnotationList newTextRangeAnnotationList) { startAnnotating(newTextRangeAnnotationList); return Response.accepted()// .location(locationBuilder.locationOf(resource, EndpointPaths.TEXT, EndpointPaths.ANNOTATIONBATCH, "status"))// .build(); } @GET @Path(EndpointPaths.ANNOTATIONBATCH + "/status") public Response getAnnotationBatchStatus() { TextAnnotationImportStatus textAnnotationsImportTaskStatus = annotationTaskStatusMap.get(resourceUUID)// .orElseThrow(NotFoundException::new); return ok(textAnnotationsImportTaskStatus); } /* private methods */ private void startTextProcessing(String xml) { TextGraphImportTask task = new TextGraphImportTask(service, locationBuilder, xml, resource); taskStatusMap.put(resource.getId(), task.getStatus()); runTask(task); } private void startAnnotating(TextRangeAnnotationList newTextRangeAnnotationList) { TextAnnotationBatchTask task = new TextAnnotationBatchTask(service, newTextRangeAnnotationList, textRangeAnnotationValidator, resource); annotationTaskStatusMap.put(resource.getId(), task.getStatus()); runTask(task); } private void runTask(Runnable task) { if (config.asynchronousEndpointsAllowed()) { executorService.execute(task); } else { // For now, for the acceptance tests. task.run(); } } private void assertResourceHasText() { if (!resource.hasText()) { throw new NotFoundException("this resource has no text"); } } private void assertResourceHasNoText() { if (resource.hasText()) { throw new ConflictException("This resource already has a text, which cannot be replaced."); } } }