Skip to content

Commit 6c94cb9

Browse files
committed
Merge branch 'ft-sparql-results-html' into develop
2 parents c9ae4c5 + 6dd8bea commit 6c94cb9

9 files changed

Lines changed: 301 additions & 78 deletions

File tree

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,13 @@
134134
<dependency>
135135
<groupId>${project.groupId}</groupId>
136136
<artifactId>client</artifactId>
137-
<version>4.0.3</version>
137+
<version>4.0.4</version>
138138
<classifier>classes</classifier>
139139
</dependency>
140140
<dependency>
141141
<groupId>${project.groupId}</groupId>
142142
<artifactId>client</artifactId>
143-
<version>4.0.3</version>
143+
<version>4.0.4</version>
144144
<type>war</type>
145145
</dependency>
146146
<!-- required by jsonld-java - version same as Jersey's HTTP Client -->

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/server/model/impl/ProxyResourceBase.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public ProxyResourceBase(@Context UriInfo uriInfo, @Context Request request, @Co
111111
:
112112
URI.create(uriInfo.getQueryParameters().getFirst(AC.uri.getLocalName())),
113113
uriInfo.getQueryParameters().getFirst(AC.endpoint.getLocalName()) == null ? null : URI.create(uriInfo.getQueryParameters().getFirst(AC.endpoint.getLocalName())),
114+
uriInfo.getQueryParameters().getFirst(AC.query.getLocalName()) == null ? null : uriInfo.getQueryParameters().getFirst(AC.query.getLocalName()),
114115
uriInfo.getQueryParameters().getFirst(AC.accept.getLocalName()) == null ? null : MediaType.valueOf(uriInfo.getQueryParameters().getFirst(AC.accept.getLocalName())),
115116
uriInfo.getQueryParameters().getFirst(AC.mode.getLocalName()) == null ? null : URI.create(uriInfo.getQueryParameters().getFirst(AC.mode.getLocalName())),
116117
system, httpServletRequest, dataManager, agentContext, providers);
@@ -127,8 +128,9 @@ public ProxyResourceBase(@Context UriInfo uriInfo, @Context Request request, @Co
127128
* @param service application's SPARQL service
128129
* @param securityContext JAX-RS security context
129130
* @param crc request context
130-
* @param uri <code>uri</code> URL param
131-
* @param endpoint <code>endpoint</code> URL param
131+
* @param uri Linked Data URI
132+
* @param endpoint SPARQL endpoint URI
133+
* @param query SPARQL query
132134
* @param accept <code>accept</code> URL param
133135
* @param mode <code>mode</code> URL param
134136
* @param system system application
@@ -140,11 +142,11 @@ public ProxyResourceBase(@Context UriInfo uriInfo, @Context Request request, @Co
140142
protected ProxyResourceBase(@Context UriInfo uriInfo, @Context Request request, @Context HttpHeaders httpHeaders, MediaTypes mediaTypes,
141143
com.atomgraph.linkeddatahub.apps.model.Application application, Optional<Service> service,
142144
@Context SecurityContext securityContext, @Context ContainerRequestContext crc,
143-
@QueryParam("uri") URI uri, @QueryParam("endpoint") URI endpoint, @QueryParam("accept") MediaType accept, @QueryParam("mode") URI mode,
145+
@QueryParam("uri") URI uri, @QueryParam("endpoint") URI endpoint, @QueryParam("query") String query, @QueryParam("accept") MediaType accept, @QueryParam("mode") URI mode,
144146
com.atomgraph.linkeddatahub.Application system, @Context HttpServletRequest httpServletRequest, DataManager dataManager, Optional<AgentContext> agentContext,
145147
@Context Providers providers)
146148
{
147-
super(uriInfo, request, httpHeaders, mediaTypes, uri, endpoint, accept, mode, system.getExternalClient(), httpServletRequest);
149+
super(uriInfo, request, httpHeaders, mediaTypes, uri, endpoint, query, accept, mode, system.getExternalClient(), httpServletRequest);
148150
this.uriInfo = uriInfo;
149151
this.application = application;
150152
this.service = service.get();
@@ -156,7 +158,7 @@ protected ProxyResourceBase(@Context UriInfo uriInfo, @Context Request request,
156158

157159
List<jakarta.ws.rs.core.MediaType> readableMediaTypesList = new ArrayList<>();
158160
readableMediaTypesList.addAll(mediaTypes.getReadable(Model.class));
159-
readableMediaTypesList.addAll(mediaTypes.getReadable(ResultSet.class)); // not in the superclass
161+
readableMediaTypesList.addAll(mediaTypes.getReadable(ResultSet.class));
160162
this.readableMediaTypes = readableMediaTypesList.toArray(MediaType[]::new);
161163

162164
if (agentContext.isPresent())

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

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

1818
import com.atomgraph.client.util.DataManager;
19+
import com.atomgraph.linkeddatahub.apps.model.EndUserApplication;
20+
import com.atomgraph.linkeddatahub.model.auth.Agent;
21+
import com.atomgraph.linkeddatahub.server.io.ValidatingModelProvider;
1922
import java.lang.annotation.Annotation;
2023
import java.lang.reflect.Type;
2124
import java.security.MessageDigest;
2225
import jakarta.inject.Singleton;
26+
import jakarta.ws.rs.InternalServerErrorException;
2327
import jakarta.ws.rs.Produces;
28+
import jakarta.ws.rs.core.EntityTag;
2429
import jakarta.ws.rs.core.MediaType;
30+
import jakarta.ws.rs.core.MultivaluedMap;
2531
import jakarta.ws.rs.ext.MessageBodyWriter;
2632
import jakarta.ws.rs.ext.Provider;
33+
import java.io.ByteArrayOutputStream;
34+
import java.io.IOException;
35+
import java.io.OutputStream;
36+
import java.math.BigInteger;
37+
import java.util.Arrays;
38+
import java.util.HashMap;
39+
import java.util.Map;
40+
import javax.xml.transform.TransformerException;
41+
import net.sf.saxon.s9api.SaxonApiException;
2742
import net.sf.saxon.s9api.XsltExecutable;
43+
import org.apache.http.HttpHeaders;
2844
import org.apache.jena.ontology.OntModelSpec;
2945
import org.apache.jena.rdf.model.Model;
46+
import org.apache.jena.riot.RDFFormat;
47+
import org.apache.jena.riot.RDFWriter;
48+
import org.apache.jena.riot.SysRIOT;
49+
import org.slf4j.Logger;
50+
import org.slf4j.LoggerFactory;
3051

3152
/**
3253
* JAX-RS writer that renders RDF as HTML using XSLT stylesheet.
@@ -36,9 +57,11 @@
3657
@Provider
3758
@Singleton
3859
@Produces({MediaType.TEXT_HTML + ";charset=UTF-8", MediaType.APPLICATION_XHTML_XML + ";charset=UTF-8"})
39-
public class ModelXSLTWriter extends ModelXSLTWriterBase implements MessageBodyWriter<Model>
60+
public class ModelXSLTWriter extends XSLTWriterBase implements MessageBodyWriter<Model>
4061
{
4162

63+
private static final Logger log = LoggerFactory.getLogger(ModelXSLTWriter.class);
64+
4265
/**
4366
* Constructs XSLT writer.
4467
*
@@ -58,4 +81,62 @@ public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotat
5881
return Model.class.isAssignableFrom(type);
5982
}
6083

84+
@Override
85+
public long getSize(Model model, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType)
86+
{
87+
return -1;
88+
}
89+
90+
@Override
91+
public void writeTo(Model model, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> headerMap, OutputStream entityStream) throws IOException
92+
{
93+
// authenticated agents get a different HTML representation and therefore a different entity tag
94+
if (headerMap.containsKey(HttpHeaders.ETAG) && headerMap.getFirst(HttpHeaders.ETAG) instanceof EntityTag && getSecurityContext() != null && getSecurityContext().getUserPrincipal() instanceof Agent)
95+
{
96+
EntityTag eTag = (EntityTag)headerMap.getFirst(HttpHeaders.ETAG);
97+
BigInteger eTagHash = new BigInteger(eTag.getValue(), 16);
98+
Agent agent = (Agent)getSecurityContext().getUserPrincipal();
99+
eTagHash = eTagHash.add(BigInteger.valueOf(agent.hashCode()));
100+
headerMap.replace(HttpHeaders.ETAG, Arrays.asList(new EntityTag(eTagHash.toString(16))));
101+
}
102+
103+
try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
104+
{
105+
Map<String, Object> properties = new HashMap<>() ;
106+
properties.put("allowBadURIs", "true"); // round-tripping RDF/POST with user input may contain invalid URIs
107+
org.apache.jena.sparql.util.Context cxt = new org.apache.jena.sparql.util.Context();
108+
cxt.set(SysRIOT.sysRdfWriterProperties, properties);
109+
110+
RDFWriter.create().
111+
format(RDFFormat.RDFXML_PLAIN).
112+
context(cxt).
113+
source(processWrite(model)).
114+
output(baos);
115+
116+
transform(baos, mediaType, headerMap, entityStream);
117+
}
118+
catch (TransformerException | SaxonApiException ex)
119+
{
120+
if (log.isErrorEnabled()) log.error("XSLT transformation failed", ex);
121+
throw new InternalServerErrorException(ex);
122+
}
123+
}
124+
125+
/**
126+
* Hook for RDF model processing before write.
127+
*
128+
* @param model RDF model
129+
* @return RDF model
130+
*/
131+
public Model processWrite(Model model)
132+
{
133+
// show foaf:mbox in end-user apps
134+
if (getApplication().get().canAs(EndUserApplication.class)) return model;
135+
// show foaf:mbox for authenticated agents
136+
if (getSecurityContext() != null && getSecurityContext().getUserPrincipal() instanceof Agent) return model;
137+
138+
// show foaf:mbox_sha1sum for all other agents (in admin apps)
139+
return ValidatingModelProvider.hashMboxes(getMessageDigest()).apply(model); // apply processing from superclasses
140+
}
141+
61142
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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.ByteArrayOutputStream;
30+
import java.io.IOException;
31+
import java.io.OutputStream;
32+
import java.lang.annotation.Annotation;
33+
import java.lang.reflect.Type;
34+
import java.math.BigInteger;
35+
import java.security.MessageDigest;
36+
import java.util.Arrays;
37+
import javax.xml.transform.TransformerException;
38+
import net.sf.saxon.s9api.SaxonApiException;
39+
import net.sf.saxon.s9api.XsltExecutable;
40+
import org.apache.http.HttpHeaders;
41+
import org.apache.jena.ontology.OntModelSpec;
42+
import org.apache.jena.query.ResultSetFormatter;
43+
import org.apache.jena.query.ResultSetRewindable;
44+
import org.slf4j.Logger;
45+
import org.slf4j.LoggerFactory;
46+
47+
/**
48+
*
49+
* @author {@literal Martynas Jusevičius <martynas@atomgraph.com>}
50+
*/
51+
@Provider
52+
@Singleton
53+
@Produces({MediaType.TEXT_HTML + ";charset=UTF-8", MediaType.APPLICATION_XHTML_XML + ";charset=UTF-8"})
54+
public class ResultSetXSLTWriter extends XSLTWriterBase implements MessageBodyWriter<ResultSetRewindable>
55+
{
56+
57+
private static final Logger log = LoggerFactory.getLogger(ResultSetXSLTWriter.class);
58+
59+
/**
60+
* Constructs XSLT writer.
61+
*
62+
* @param xsltExec compiled XSLT stylesheet
63+
* @param ontModelSpec ontology specification
64+
* @param dataManager RDF data manager
65+
* @param messageDigest message digest
66+
*/
67+
public ResultSetXSLTWriter(XsltExecutable xsltExec, OntModelSpec ontModelSpec, DataManager dataManager, MessageDigest messageDigest)
68+
{
69+
super(xsltExec, ontModelSpec, dataManager, messageDigest);
70+
}
71+
72+
@Override
73+
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType)
74+
{
75+
return (ResultSetRewindable.class.isAssignableFrom(type));
76+
}
77+
78+
@Override
79+
public void writeTo(ResultSetRewindable results, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> headerMap, OutputStream entityStream) throws IOException, WebApplicationException
80+
{
81+
if (log.isTraceEnabled()) log.trace("Writing ResultSet with HTTP headers: {} MediaType: {}", headerMap, mediaType);
82+
83+
try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
84+
{
85+
// authenticated agents get a different HTML representation and therefore a different entity tag
86+
if (headerMap.containsKey(HttpHeaders.ETAG) && headerMap.getFirst(HttpHeaders.ETAG) instanceof EntityTag && getSecurityContext() != null && getSecurityContext().getUserPrincipal() instanceof Agent)
87+
{
88+
EntityTag eTag = (EntityTag)headerMap.getFirst(HttpHeaders.ETAG);
89+
BigInteger eTagHash = new BigInteger(eTag.getValue(), 16);
90+
Agent agent = (Agent)getSecurityContext().getUserPrincipal();
91+
eTagHash = eTagHash.add(BigInteger.valueOf(agent.hashCode()));
92+
headerMap.replace(HttpHeaders.ETAG, Arrays.asList(new EntityTag(eTagHash.toString(16))));
93+
}
94+
95+
ResultSetFormatter.outputAsXML(baos, results);
96+
97+
transform(baos, mediaType, headerMap, entityStream);
98+
}
99+
catch (TransformerException | SaxonApiException ex)
100+
{
101+
if (log.isErrorEnabled()) log.error("XSLT transformation failed", ex);
102+
throw new WebApplicationException(ex, Response.Status.INTERNAL_SERVER_ERROR); // TO-DO: make Mapper
103+
}
104+
}
105+
106+
}

0 commit comments

Comments
 (0)