diff --git a/src/main/java/info/textgrid/services/aggregator/html/HTML.java b/src/main/java/info/textgrid/services/aggregator/html/HTML.java index 2407888a0d9f0707a45da60b10d07e080c5aa11a..80258158d233ea4306c9fe335d9b4bf05ba96426 100644 --- a/src/main/java/info/textgrid/services/aggregator/html/HTML.java +++ b/src/main/java/info/textgrid/services/aggregator/html/HTML.java @@ -28,7 +28,8 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; -import javax.ws.rs.core.StreamingOutput; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; import javax.xml.transform.stream.StreamSource; import net.sf.saxon.s9api.Processor; @@ -199,7 +200,7 @@ private static boolean isPublic(final ObjectType metadata) { @GET @Path(value = "/{object}") @Produces(value = "text/xml") - public StreamingOutput get( + public Response get( @Description("The TextGrid URI of the TEI document or aggregation to transform") @PathParam("object") final URI uri, @Description("If given, an alternative XSLT stylesheet to use") @@ -211,17 +212,17 @@ public StreamingOutput get( @QueryParam("sid") final String sid, @Description("If true, pass the information the stylesheet that its result will be embedded into some website") @QueryParam("embedded") final boolean embedded, @Description("URL of the CSS that should be referenced in the HTML that is created") - @QueryParam("css") final URI css) throws ObjectNotFoundFault, + @QueryParam("css") final URI css, + @Context final Request request) throws ObjectNotFoundFault, MetadataParseFault, IoFault, AuthFault, ProtocolNotImplementedFault, WebApplicationException, IOException, SaxonApiException, ExecutionException { logger.fine("HTML called for root object: " + uri); + final HTMLWriter writer = new HTMLWriter(this, uri, xsluri, - refreshStylesheet, pi, embedded, css, sid); - writer.loadSource(); - writer.loadStylesheet(); - return writer; + refreshStylesheet, pi, embedded, css, sid, request); + return writer.createResponse().build(); } } diff --git a/src/main/java/info/textgrid/services/aggregator/html/HTMLWriter.java b/src/main/java/info/textgrid/services/aggregator/html/HTMLWriter.java index 0b6241a78d742be0dc3f3f06faa019e15c103c15..ea63cd990e2140e69033e52b1172dfa54143d6a1 100644 --- a/src/main/java/info/textgrid/services/aggregator/html/HTMLWriter.java +++ b/src/main/java/info/textgrid/services/aggregator/html/HTMLWriter.java @@ -10,6 +10,7 @@ import info.textgrid.services.aggregator.ITextGridRep; import info.textgrid.services.aggregator.ITextGridRep.TGOSupplier; import info.textgrid.services.aggregator.teicorpus.TEICorpusSerializer; +import info.textgrid.utils.export.filenames.DefaultFilenamePolicy; import java.io.IOException; import java.io.InputStream; @@ -21,6 +22,11 @@ import java.util.logging.Logger; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.CacheControl; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.StreamingOutput; import javax.xml.transform.Source; @@ -111,6 +117,10 @@ private enum SourceType { private String actualStylesheet; + private TGOSupplier<InputStream> content; + + private Request request; + // Constructor and configuration public HTMLWriter(final HTML service, final URI rootURI) { @@ -125,7 +135,7 @@ public HTMLWriter(final HTML service, final URI rootURI) { public HTMLWriter(final HTML service, final URI rootURI, final URI stylesheetURI, final boolean refreshStylesheet, final boolean readStylesheetPI, final boolean embedded, - final URI css, final String sid) { + final URI css, final String sid, final Request request) { this(service, rootURI); @@ -135,6 +145,7 @@ public HTMLWriter(final HTML service, final URI rootURI, this.embedded = embedded; this.css = Optional.fromNullable(css); this.sid(sid); + this.request = request; } public HTMLWriter sid(final String sid) { @@ -173,9 +184,7 @@ protected HTMLWriter loadSource() throws ObjectNotFoundFault, MetadataParseFault, IoFault, ProtocolNotImplementedFault, AuthFault, IOException { - TGOSupplier<InputStream> content = null; - content = service.repository.read(rootURI, sid.orNull()); - metadata = content.getMetadata(); + metadata = getContent().getMetadata(); final String format = metadata.getGeneric().getProvided().getFormat(); if (format.contains("aggregation")) { sourceType = SourceType.AGGREGATION; @@ -202,11 +211,11 @@ protected HTMLWriter loadSource() throws ObjectNotFoundFault, } else if (sourceType == SourceType.XML && readStylesheetPI) { final FileBackedOutputStream xmlBuffer = new FileBackedOutputStream( 1024 * 1024, true); - ByteStreams.copy(content, xmlBuffer); + ByteStreams.copy(getContent(), xmlBuffer); detectEmbeddedStylesheet(xmlBuffer.getSupplier().getInput()); this.source = new StreamSource(xmlBuffer.getSupplier().getInput()); } else { - this.source = new StreamSource(content.getInput(), + this.source = new StreamSource(getContent().getInput(), rootURI.toString()); } logger.log(Level.INFO, MessageFormat.format("Fetched source for {0}, type={1}, after {2}", rootURI, sourceType, stopwatch.toString())); @@ -214,6 +223,14 @@ protected HTMLWriter loadSource() throws ObjectNotFoundFault, return this; } + private TGOSupplier<InputStream> getContent() { + if (content == null) { + content = service.repository.read(rootURI, sid.orNull()); + logger.info(MessageFormat.format("Fetched source for {0} up to metadata after {1}", rootURI, stopwatch)); + } + return content; + } + private void detectEmbeddedStylesheet(final InputStream input) { try { final Source associatedStylesheet = TransformerFactory @@ -302,4 +319,30 @@ public void write(final OutputStream out) throws IOException, stopwatch.stop(); } + public ResponseBuilder createResponse() throws ObjectNotFoundFault, MetadataParseFault, IoFault, ProtocolNotImplementedFault, AuthFault, IOException, SaxonApiException { + if (!refreshStylesheet && request != null) { + final ResponseBuilder builder = request.evaluatePreconditions(getContent().getMetadata().getGeneric().getGenerated().getLastModified().toGregorianCalendar().getTime()); + if (builder != null) { + logger.info(MessageFormat.format("Aborting: client already has HTML for {0} (after {1})", rootURI, stopwatch)); + return builder; + } else { + logger.info("Creating new transformation."); + } + } + + loadSource(); + loadStylesheet(); + + final ResponseBuilder builder = Response.ok(); + builder.type(MediaType.TEXT_XML_TYPE); + builder.lastModified(getContent().getMetadata().getGeneric().getGenerated().getLastModified().toGregorianCalendar().getTime()); + final CacheControl cacheControl = new CacheControl(); + cacheControl.setPrivate(sid.isPresent()); + cacheControl.setMaxAge(86400); // one day + builder.cacheControl(cacheControl); + builder.header("Content-Disposition", "inline;filename=\"" + DefaultFilenamePolicy.INSTANCE.getFilename(getContent().getMetadata(), false) + ".html\""); + builder.entity(this); + return builder; + } + }