Skip to content

Commit 1c138d3

Browse files
committed
ModelXSLTWriterBase renamed to XSLTWriterBase
New `ResultSetXSLTWriter` message body writer
1 parent f93578a commit 1c138d3

4 files changed

Lines changed: 180 additions & 30 deletions

File tree

src/main/java/com/atomgraph/linkeddatahub/Application.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@
120120
import com.atomgraph.linkeddatahub.vocabulary.Google;
121121
import com.atomgraph.linkeddatahub.vocabulary.LAPP;
122122
import com.atomgraph.linkeddatahub.writer.Mode;
123-
import com.atomgraph.linkeddatahub.writer.ModelXSLTWriterBase;
123+
import com.atomgraph.linkeddatahub.writer.ResultSetXSLTWriter;
124+
import com.atomgraph.linkeddatahub.writer.XSLTWriterBase;
124125
import com.atomgraph.linkeddatahub.writer.factory.ModeFactory;
125126
import com.atomgraph.linkeddatahub.writer.function.DecodeURI;
126127
import com.atomgraph.processor.vocabulary.AP;
@@ -723,10 +724,10 @@ protected PasswordAuthentication getPasswordAuthentication()
723724
}
724725

725726
// register HTTPS URL of translations.rdf so it doesn't have to be requested repeatedly
726-
try (InputStream translations = servletConfig.getServletContext().getResourceAsStream(ModelXSLTWriterBase.TRANSLATIONS_PATH))
727+
try (InputStream translations = servletConfig.getServletContext().getResourceAsStream(XSLTWriterBase.TRANSLATIONS_PATH))
727728
{
728729
TreeInfo doc = xsltProc.getUnderlyingConfiguration().buildDocumentTree(new StreamSource(translations));
729-
xsltProc.getUnderlyingConfiguration().getGlobalDocumentPool().add(doc, baseURI.resolve(ModelXSLTWriterBase.TRANSLATIONS_PATH).toString());
730+
xsltProc.getUnderlyingConfiguration().getGlobalDocumentPool().add(doc, baseURI.resolve(XSLTWriterBase.TRANSLATIONS_PATH).toString());
730731
}
731732
}
732733
catch (XPathException | TransformerException ex)
@@ -808,6 +809,7 @@ public void init()
808809
register(new QueryParamProvider());
809810
register(new UpdateRequestProvider());
810811
register(new ModelXSLTWriter(getXsltExecutable(), getOntModelSpec(), getDataManager(), getMessageDigest())); // writes (X)HTML responses
812+
register(new ResultSetXSLTWriter(getXsltExecutable(), getOntModelSpec(), getDataManager(), getMessageDigest())); // writes (X)HTML responses
811813

812814
final com.atomgraph.linkeddatahub.Application system = this;
813815
register(new AbstractBinder()

src/main/java/com/atomgraph/linkeddatahub/writer/ModelXSLTWriter.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,23 @@
1616
package com.atomgraph.linkeddatahub.writer;
1717

1818
import com.atomgraph.client.util.DataManager;
19+
import com.atomgraph.linkeddatahub.model.auth.Agent;
1920
import java.lang.annotation.Annotation;
2021
import java.lang.reflect.Type;
2122
import java.security.MessageDigest;
2223
import jakarta.inject.Singleton;
2324
import jakarta.ws.rs.Produces;
25+
import jakarta.ws.rs.core.EntityTag;
2426
import jakarta.ws.rs.core.MediaType;
27+
import jakarta.ws.rs.core.MultivaluedMap;
2528
import jakarta.ws.rs.ext.MessageBodyWriter;
2629
import jakarta.ws.rs.ext.Provider;
30+
import java.io.IOException;
31+
import java.io.OutputStream;
32+
import java.math.BigInteger;
33+
import java.util.Arrays;
2734
import net.sf.saxon.s9api.XsltExecutable;
35+
import org.apache.http.HttpHeaders;
2836
import org.apache.jena.ontology.OntModelSpec;
2937
import org.apache.jena.rdf.model.Model;
3038

@@ -36,7 +44,7 @@
3644
@Provider
3745
@Singleton
3846
@Produces({MediaType.TEXT_HTML + ";charset=UTF-8", MediaType.APPLICATION_XHTML_XML + ";charset=UTF-8"})
39-
public class ModelXSLTWriter extends ModelXSLTWriterBase implements MessageBodyWriter<Model>
47+
public class ModelXSLTWriter extends XSLTWriterBase implements MessageBodyWriter<Model>
4048
{
4149

4250
/**
@@ -58,4 +66,26 @@ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotat
5866
return Model.class.isAssignableFrom(type);
5967
}
6068

69+
@Override
70+
public long getSize(Model model, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType)
71+
{
72+
return -1;
73+
}
74+
75+
@Override
76+
public void writeTo(Model model, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> headerMap, OutputStream entityStream) throws IOException
77+
{
78+
// authenticated agents get a different HTML representation and therefore a different entity tag
79+
if (headerMap.containsKey(HttpHeaders.ETAG) && headerMap.getFirst(HttpHeaders.ETAG) instanceof EntityTag && getSecurityContext() != null && getSecurityContext().getUserPrincipal() instanceof Agent)
80+
{
81+
EntityTag eTag = (EntityTag)headerMap.getFirst(HttpHeaders.ETAG);
82+
BigInteger eTagHash = new BigInteger(eTag.getValue(), 16);
83+
Agent agent = (Agent)getSecurityContext().getUserPrincipal();
84+
eTagHash = eTagHash.add(BigInteger.valueOf(agent.hashCode()));
85+
headerMap.replace(HttpHeaders.ETAG, Arrays.asList(new EntityTag(eTagHash.toString(16))));
86+
}
87+
88+
super.writeTo(processWrite(model), type, type, annotations, mediaType, headerMap, entityStream);
89+
}
90+
6191
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright 2023 Martynas Jusevičius <martynas@atomgraph.com>.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.atomgraph.linkeddatahub.writer;
17+
18+
import com.atomgraph.client.util.DataManager;
19+
import com.atomgraph.linkeddatahub.model.auth.Agent;
20+
import jakarta.inject.Singleton;
21+
import jakarta.ws.rs.Produces;
22+
import jakarta.ws.rs.WebApplicationException;
23+
import jakarta.ws.rs.core.EntityTag;
24+
import jakarta.ws.rs.core.MediaType;
25+
import jakarta.ws.rs.core.MultivaluedMap;
26+
import jakarta.ws.rs.core.Response;
27+
import jakarta.ws.rs.ext.MessageBodyWriter;
28+
import jakarta.ws.rs.ext.Provider;
29+
import java.io.ByteArrayInputStream;
30+
import java.io.ByteArrayOutputStream;
31+
import java.io.IOException;
32+
import java.io.OutputStream;
33+
import java.lang.annotation.Annotation;
34+
import java.lang.reflect.Type;
35+
import java.math.BigInteger;
36+
import static java.nio.charset.StandardCharsets.UTF_8;
37+
import java.security.MessageDigest;
38+
import java.time.ZonedDateTime;
39+
import java.util.Arrays;
40+
import javax.xml.transform.TransformerException;
41+
import javax.xml.transform.URIResolver;
42+
import javax.xml.transform.stream.StreamSource;
43+
import net.sf.saxon.lib.UnparsedTextURIResolver;
44+
import net.sf.saxon.s9api.SaxonApiException;
45+
import net.sf.saxon.s9api.Serializer;
46+
import net.sf.saxon.s9api.Xslt30Transformer;
47+
import net.sf.saxon.s9api.XsltExecutable;
48+
import net.sf.saxon.value.DateTimeValue;
49+
import org.apache.http.HttpHeaders;
50+
import org.apache.jena.ontology.OntModelSpec;
51+
import org.apache.jena.query.ResultSet;
52+
import org.apache.jena.query.ResultSetFormatter;
53+
import org.apache.jena.rdf.model.Model;
54+
import org.slf4j.Logger;
55+
import org.slf4j.LoggerFactory;
56+
57+
/**
58+
*
59+
* @author {@literal Martynas Jusevičius <martynas@atomgraph.com>}
60+
*/
61+
@Provider
62+
@Singleton
63+
@Produces({MediaType.TEXT_HTML + ";charset=UTF-8", MediaType.APPLICATION_XHTML_XML + ";charset=UTF-8"})
64+
public class ResultSetXSLTWriter extends XSLTWriterBase implements MessageBodyWriter<ResultSet>
65+
{
66+
67+
private static final Logger log = LoggerFactory.getLogger(ResultSetXSLTWriter.class);
68+
69+
/**
70+
* Constructs XSLT writer.
71+
*
72+
* @param xsltExec compiled XSLT stylesheet
73+
* @param ontModelSpec ontology specification
74+
* @param dataManager RDF data manager
75+
* @param messageDigest message digest
76+
*/
77+
public ResultSetXSLTWriter(XsltExecutable xsltExec, OntModelSpec ontModelSpec, DataManager dataManager, MessageDigest messageDigest)
78+
{
79+
super(xsltExec, ontModelSpec, dataManager, messageDigest);
80+
}
81+
82+
@Override
83+
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType)
84+
{
85+
return (ResultSet.class.isAssignableFrom(type));
86+
}
87+
88+
@Override
89+
public void writeTo(ResultSet results, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> headerMap, OutputStream entityStream) throws IOException, WebApplicationException
90+
{
91+
if (log.isTraceEnabled()) log.trace("Writing ResultSet with HTTP headers: {} MediaType: {}", headerMap, mediaType);
92+
93+
try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
94+
{
95+
// authenticated agents get a different HTML representation and therefore a different entity tag
96+
if (headerMap.containsKey(HttpHeaders.ETAG) && headerMap.getFirst(HttpHeaders.ETAG) instanceof EntityTag && getSecurityContext() != null && getSecurityContext().getUserPrincipal() instanceof Agent)
97+
{
98+
EntityTag eTag = (EntityTag)headerMap.getFirst(HttpHeaders.ETAG);
99+
BigInteger eTagHash = new BigInteger(eTag.getValue(), 16);
100+
Agent agent = (Agent)getSecurityContext().getUserPrincipal();
101+
eTagHash = eTagHash.add(BigInteger.valueOf(agent.hashCode()));
102+
headerMap.replace(HttpHeaders.ETAG, Arrays.asList(new EntityTag(eTagHash.toString(16))));
103+
}
104+
105+
ResultSetFormatter.outputAsXML(baos, results);
106+
107+
Xslt30Transformer xsltTrans = getXsltExecutable().load30();
108+
Serializer out = xsltTrans.newSerializer();
109+
out.setOutputStream(entityStream);
110+
out.setOutputProperty(Serializer.Property.ENCODING, UTF_8.name());
111+
112+
if (mediaType.isCompatible(MediaType.TEXT_HTML_TYPE))
113+
{
114+
out.setOutputProperty(Serializer.Property.METHOD, "html");
115+
out.setOutputProperty(Serializer.Property.MEDIA_TYPE, MediaType.TEXT_HTML);
116+
out.setOutputProperty(Serializer.Property.DOCTYPE_SYSTEM, "http://www.w3.org/TR/html4/strict.dtd");
117+
out.setOutputProperty(Serializer.Property.DOCTYPE_PUBLIC, "-//W3C//DTD HTML 4.01//EN");
118+
}
119+
if (mediaType.isCompatible(MediaType.APPLICATION_XHTML_XML_TYPE))
120+
{
121+
out.setOutputProperty(Serializer.Property.METHOD, "xhtml");
122+
out.setOutputProperty(Serializer.Property.MEDIA_TYPE, MediaType.APPLICATION_XHTML_XML);
123+
out.setOutputProperty(Serializer.Property.DOCTYPE_SYSTEM, "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
124+
out.setOutputProperty(Serializer.Property.DOCTYPE_PUBLIC, "-//W3C//DTD XHTML 1.0 Strict//EN");
125+
}
126+
127+
xsltTrans.setURIResolver((URIResolver)getDataManager());
128+
xsltTrans.getUnderlyingController().setUnparsedTextURIResolver((UnparsedTextURIResolver)getDataManager());
129+
xsltTrans.getUnderlyingController().setCurrentDateTime(DateTimeValue.fromZonedDateTime(ZonedDateTime.now())); // TO-DO: make TZ configurable
130+
xsltTrans.setStylesheetParameters(getParameters(headerMap));
131+
xsltTrans.transform(new StreamSource(new ByteArrayInputStream(baos.toByteArray())), out);
132+
}
133+
catch (TransformerException | SaxonApiException ex)
134+
{
135+
if (log.isErrorEnabled()) log.error("XSLT transformation failed", ex);
136+
throw new WebApplicationException(ex, Response.Status.INTERNAL_SERVER_ERROR); // TO-DO: make Mapper
137+
}
138+
}
139+
140+
}

src/main/java/com/atomgraph/linkeddatahub/writer/ModelXSLTWriterBase.java renamed to src/main/java/com/atomgraph/linkeddatahub/writer/XSLTWriterBase.java

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@
3636
import com.atomgraph.linkeddatahub.vocabulary.FOAF;
3737
import com.atomgraph.linkeddatahub.vocabulary.LDHC;
3838
import java.io.IOException;
39-
import java.io.OutputStream;
40-
import java.lang.annotation.Annotation;
41-
import java.lang.reflect.Type;
42-
import java.math.BigInteger;
4339
import java.net.URI;
4440
import java.net.URISyntaxException;
4541
import java.security.MessageDigest;
@@ -53,8 +49,6 @@
5349
import jakarta.inject.Inject;
5450
import jakarta.ws.rs.container.ContainerRequestContext;
5551
import jakarta.ws.rs.core.Context;
56-
import jakarta.ws.rs.core.EntityTag;
57-
import jakarta.ws.rs.core.MediaType;
5852
import jakarta.ws.rs.core.MultivaluedMap;
5953
import jakarta.ws.rs.core.SecurityContext;
6054
import javax.xml.transform.Source;
@@ -78,9 +72,9 @@
7872
*
7973
* @author Martynas Jusevičius {@literal <martynas@atomgraph.com>}
8074
*/
81-
public abstract class ModelXSLTWriterBase extends com.atomgraph.client.writer.ModelXSLTWriterBase
75+
public abstract class XSLTWriterBase extends com.atomgraph.client.writer.ModelXSLTWriterBase
8276
{
83-
private static final Logger log = LoggerFactory.getLogger(ModelXSLTWriterBase.class);
77+
private static final Logger log = LoggerFactory.getLogger(XSLTWriterBase.class);
8478
private static final Set<String> NAMESPACES;
8579
/** The relative URL of the RDF file with localized labels */
8680
public static final String TRANSLATIONS_PATH = "static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/translations.rdf";
@@ -112,28 +106,12 @@ public abstract class ModelXSLTWriterBase extends com.atomgraph.client.writer.Mo
112106
* @param dataManager RDF data manager
113107
* @param messageDigest message digest
114108
*/
115-
public ModelXSLTWriterBase(XsltExecutable xsltExec, OntModelSpec ontModelSpec, DataManager dataManager, MessageDigest messageDigest)
109+
public XSLTWriterBase(XsltExecutable xsltExec, OntModelSpec ontModelSpec, DataManager dataManager, MessageDigest messageDigest)
116110
{
117111
super(xsltExec, ontModelSpec, dataManager); // this DataManager will be unused as we override getDataManager() with the injected (subclassed) one
118112
this.messageDigest = messageDigest;
119113
}
120-
121-
@Override
122-
public void writeTo(Model model, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> headerMap, OutputStream entityStream) throws IOException
123-
{
124-
// authenticated agents get a different HTML representation and therefore a different entity tag
125-
if (headerMap.containsKey(HttpHeaders.ETAG) && headerMap.getFirst(HttpHeaders.ETAG) instanceof EntityTag && getSecurityContext() != null && getSecurityContext().getUserPrincipal() instanceof Agent)
126-
{
127-
EntityTag eTag = (EntityTag)headerMap.getFirst(HttpHeaders.ETAG);
128-
BigInteger eTagHash = new BigInteger(eTag.getValue(), 16);
129-
Agent agent = (Agent)getSecurityContext().getUserPrincipal();
130-
eTagHash = eTagHash.add(BigInteger.valueOf(agent.hashCode()));
131-
headerMap.replace(HttpHeaders.ETAG, Arrays.asList(new EntityTag(eTagHash.toString(16))));
132-
}
133-
134-
super.writeTo(processWrite(model), type, type, annotations, mediaType, headerMap, entityStream);
135-
}
136-
114+
137115
@Override
138116
public <T extends XdmValue> Map<QName, XdmValue> getParameters(MultivaluedMap<String, Object> headerMap) throws TransformerException
139117
{

0 commit comments

Comments
 (0)