Skip to content

Commit 6a93b66

Browse files
committed
ldh:href() usage fixes, Link header parsing fixes
1 parent d2385d3 commit 6a93b66

6 files changed

Lines changed: 55 additions & 64 deletions

File tree

src/main/java/com/atomgraph/linkeddatahub/server/filter/response/ResponseHeadersFilter.java

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import jakarta.ws.rs.container.ContainerResponseFilter;
4040
import jakarta.ws.rs.core.HttpHeaders;
4141
import jakarta.ws.rs.core.Response;
42+
import java.util.regex.Pattern;
4243
import org.slf4j.Logger;
4344
import org.slf4j.LoggerFactory;
4445

@@ -52,6 +53,7 @@ public class ResponseHeadersFilter implements ContainerResponseFilter
5253
{
5354

5455
private static final Logger log = LoggerFactory.getLogger(ResponseHeadersFilter.class);
56+
private static final Pattern LINK_SPLITTER = Pattern.compile(",(?=\\s*<)"); // split on commas before next '<'
5557

5658
@Inject jakarta.inject.Provider<Application> app;
5759
@Inject jakarta.inject.Provider<Optional<Dataset>> dataset;
@@ -73,8 +75,10 @@ public void filter(ContainerRequestContext request, ContainerResponseContext res
7375
getAuthorizationContext().get().getModeURIs().forEach(mode -> response.getHeaders().add(HttpHeaders.LINK, new Link(mode, ACL.mode.getURI(), null)));
7476

7577
List<Object> linkValues = response.getHeaders().get(HttpHeaders.LINK);
78+
List<Link> links = parseLinkHeaderValues(linkValues);
79+
7680
// check whether Link rel=ldt:base is not already set. Link headers might be forwarded by ProxyResourceBase
77-
if (getLinksByRel(linkValues, LDT.base.getURI()).isEmpty())
81+
if (getLinksByRel(links, LDT.base.getURI()).isEmpty())
7882
{
7983
// add Link rel=ldt:base
8084
response.getHeaders().add(HttpHeaders.LINK, new Link(getApplication().getBaseURI(), LDT.base.getURI(), null));
@@ -91,7 +95,7 @@ public void filter(ContainerRequestContext request, ContainerResponseContext res
9195
else
9296
{
9397
// add Link rel=sd:endpoint.
94-
if (getLinksByRel(linkValues, SD.endpoint.getURI()).isEmpty() && getDataset().isPresent() && getDataset().get().getService() != null)
98+
if (getLinksByRel(links, SD.endpoint.getURI()).isEmpty() && getDataset().isPresent() && getDataset().get().getService() != null)
9599
response.getHeaders().add(HttpHeaders.LINK, new Link(URI.create(getDataset().get().getService().getSPARQLEndpoint().getURI()), SD.endpoint.getURI(), null));
96100
}
97101

@@ -104,29 +108,52 @@ public void filter(ContainerRequestContext request, ContainerResponseContext res
104108
}
105109

106110
/**
107-
* Filters <code>Link</code> headers by their <code>rel</code> attribute.
111+
* Parses HTTP <code>Link</code> headers into individual {@link Link} objects.
112+
*
113+
* Handles both multiple header fields and comma-separated values
114+
* within a single header field.
115+
*
116+
* @param linkValues raw <code>Link</code> header values (may contain multiple entries)
117+
* @return flat list of parsed {@link Link} objects
118+
*/
119+
protected List<Link> parseLinkHeaderValues(List<Object> linkValues)
120+
{
121+
List<Link> out = new ArrayList<>();
122+
if (linkValues == null) return out;
123+
124+
for (Object hv : linkValues)
125+
{
126+
String[] parts = LINK_SPLITTER.split(hv.toString());
127+
for (String part : parts)
128+
{
129+
try
130+
{
131+
out.add(Link.valueOf(part.trim()));
132+
}
133+
catch (URISyntaxException e)
134+
{
135+
// ignore invalid entries
136+
}
137+
}
138+
}
139+
140+
return out;
141+
}
142+
143+
/**
144+
* Returns all <code>Link</code> headers that match the given <code>rel</code> attribute.
108145
*
109-
* @param linkValues header list
146+
* @param links link list
110147
* @param rel <code>rel</code> value
111148
* @return filtered header list
112149
*/
113-
protected List<Link> getLinksByRel(List<Object> linkValues, String rel)
150+
protected List<Link> getLinksByRel(List<Link> links, String rel)
114151
{
115-
List relLinks = new ArrayList<>();
116-
117-
if (linkValues != null) linkValues.forEach(linkValue -> {
118-
try
119-
{
120-
Link link = Link.valueOf(linkValue.toString());
121-
if (link.getRel().equals(rel)) relLinks.add(link);
122-
}
123-
catch (URISyntaxException ex)
124-
{
125-
// ignore invalid Link headers
126-
}
127-
});
128-
129-
return relLinks;
152+
return links == null
153+
? List.of()
154+
: links.stream()
155+
.filter(link -> rel.equals(link.getRel()))
156+
.toList();
130157
}
131158

132159
/**

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/block/object.xsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ exclude-result-prefixes="#all"
183183
<xsl:message>ldh:block-object-value-response $resource-uri: <xsl:value-of select="$resource-uri"/></xsl:message>
184184
<xsl:variable name="object-uris" select="distinct-values($resource/*/@rdf:resource[starts-with(., $ldt:base)][not(key('resources', ., root($resource)))])" as="xs:string*"/>
185185
<xsl:variable name="query-string" select="$object-metadata-query || ' VALUES $this { ' || string-join(for $uri in $object-uris return '&lt;' || $uri || '&gt;', ' ') || ' }'" as="xs:string"/>
186-
<xsl:variable name="request" select="map{ 'method': 'POST', 'href': sd:endpoint(), 'media-type': 'application/sparql-query', 'body': $query-string, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
186+
<xsl:variable name="request" select="map{ 'method': 'POST', 'href': ldh:href(sd:endpoint()), 'media-type': 'application/sparql-query', 'body': $query-string, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
187187
<xsl:sequence select="
188188
map{
189189
'request': $request,

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/block/view.xsl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ exclude-result-prefixes="#all"
155155
<xsl:when test="$endpoint = sd:endpoint()">
156156
<xsl:variable name="object-uris" select="distinct-values($results/rdf:RDF/rdf:Description/*/@rdf:resource[not(key('resources', .))])" as="xs:string*"/>
157157
<xsl:variable name="query-string" select="$object-metadata-query || ' VALUES $this { ' || string-join(for $uri in $object-uris return '&lt;' || $uri || '&gt;', ' ') || ' }'" as="xs:string"/>
158-
<xsl:variable name="request" select="map{ 'method': 'POST', 'href': $endpoint, 'media-type': 'application/sparql-query', 'body': $query-string, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
158+
<xsl:variable name="request" select="map{ 'method': 'POST', 'href': ldh:href($endpoint), 'media-type': 'application/sparql-query', 'body': $query-string, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
159159
<xsl:sequence select="map:merge(($context, map{ 'request': $request , 'response': () , 'results': $results }), map{ 'duplicates': 'use-last' })"/>
160160
</xsl:when>
161161
<xsl:otherwise>
@@ -813,7 +813,7 @@ exclude-result-prefixes="#all"
813813
<xsl:if test="json:string[@key = 'predicate']">
814814
<xsl:variable name="id" select="generate-id()" as="xs:string"/>
815815
<xsl:variable name="predicate" select="json:string[@key = 'predicate']" as="xs:anyURI"/>
816-
<xsl:variable name="request-uri" select="ac:build-uri($ldt:base, map{ 'uri': string($predicate), 'accept': 'application/rdf+xml' })" as="xs:anyURI"/>
816+
<xsl:variable name="request-uri" select="ac:build-uri($ldt:base, map{ 'uri': ac:document-uri($predicate), 'accept': 'application/rdf+xml' })" as="xs:anyURI"/>
817817
<xsl:variable name="request" select="map{ 'method': 'GET', 'href': $request-uri, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
818818
<xsl:variable name="context" as="map(*)" select="
819819
map{
@@ -880,7 +880,7 @@ exclude-result-prefixes="#all"
880880
<xsl:variable name="subject-var-name" select="json:string[@key = 'subject']/substring-after(., '?')" as="xs:string"/>
881881
<xsl:variable name="predicate" select="json:string[@key = 'predicate']" as="xs:anyURI"/>
882882
<xsl:variable name="object-var-name" select="json:string[@key = 'object']/substring-after(., '?')" as="xs:string"/>
883-
<xsl:variable name="request-uri" select="ac:build-uri($ldt:base, map{ 'uri': string($predicate), 'accept': 'application/rdf+xml' })" as="xs:anyURI"/>
883+
<xsl:variable name="request-uri" select="ac:build-uri($ldt:base, map{ 'uri': ac:document-uri($predicate), 'accept': 'application/rdf+xml' })" as="xs:anyURI"/>
884884
<xsl:variable name="request" select="map{ 'method': 'GET', 'href': $request-uri, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
885885
<xsl:variable name="context" as="map(*)" select="
886886
map{
@@ -1177,7 +1177,7 @@ exclude-result-prefixes="#all"
11771177
<xsl:sequence select="ixsl:call(ixsl:get(., 'classList'), 'toggle', [ 'active', true() ])[current-date() lt xs:date('2000-01-01')]"/>
11781178
</xsl:for-each>
11791179

1180-
<xsl:variable name="request" select="map{ 'method': 'POST', 'href': sd:endpoint(), 'media-type': 'application/sparql-query', 'body': $query-string, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
1180+
<xsl:variable name="request" select="map{ 'method': 'POST', 'href': ldh:href(sd:endpoint()), 'media-type': 'application/sparql-query', 'body': $query-string, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
11811181
<xsl:variable name="context" as="map(*)">
11821182
<xsl:call-template name="ldh:RenderView">
11831183
<xsl:with-param name="block" select="$block"/>
@@ -1861,7 +1861,7 @@ exclude-result-prefixes="#all"
18611861

18621862
<xsl:for-each-group select="$results/rdf:RDF/*[@rdf:about = $var-name-resources]/*[@rdf:resource or @rdf:nodeID]" group-by="concat(namespace-uri(), local-name())">
18631863
<xsl:variable name="predicate" select="xs:anyURI(namespace-uri() || local-name())" as="xs:anyURI"/>
1864-
<xsl:variable name="request-uri" select="ac:build-uri($ldt:base, map{ 'uri': $predicate, 'accept': 'application/rdf+xml' })" as="xs:anyURI"/>
1864+
<xsl:variable name="request-uri" select="ac:build-uri($ldt:base, map{ 'uri': ac:document-uri($predicate), 'accept': 'application/rdf+xml' })" as="xs:anyURI"/>
18651865
<xsl:variable name="request" select="map{ 'method': 'GET', 'href': $request-uri, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
18661866
<xsl:variable name="context" select="map:merge((
18671867
$context,
@@ -1988,7 +1988,7 @@ exclude-result-prefixes="#all"
19881988
<xsl:for-each select="$results//srx:result[srx:binding[@name = $object-var-name]]">
19891989
<xsl:variable name="object-type" select="srx:binding[@name = $object-var-name]/srx:uri" as="xs:anyURI"/>
19901990
<xsl:variable name="value-result" select="." as="element()"/>
1991-
<xsl:variable name="request-uri" select="ac:build-uri($ldt:base, map{ 'uri': $object-type, 'accept': 'application/rdf+xml' })" as="xs:anyURI"/>
1991+
<xsl:variable name="request-uri" select="ac:build-uri($ldt:base, map{ 'uri': ac:document-uri($object-type), 'accept': 'application/rdf+xml' })" as="xs:anyURI"/>
19921992
<xsl:variable name="request" select="map{ 'method': 'GET', 'href': $request-uri, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
19931993
<xsl:variable name="context" as="map(*)" select="
19941994
map{

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/form.xsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ WHERE
274274
<ixsl:set-property name="block-html" select="ixsl:call($block, 'cloneNode', [ true() ])" object="ixsl:get(ixsl:get(ixsl:window(), 'LinkedDataHub.contents'), '`' || $about || '`')"/>
275275

276276
<!-- if the URI is external, dereference it through the proxy -->
277-
<xsl:variable name="request-uri" select="ldh:href(ac:absolute-path(ldh:base-uri(.)), map{})" as="xs:anyURI"/>
277+
<xsl:variable name="request-uri" select="ldh:href(ac:document-uri(ldh:base-uri(.)))" as="xs:anyURI"/>
278278
<xsl:variable name="request" select="map{ 'method': 'GET', 'href': $request-uri, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
279279
<xsl:variable name="context" as="map(*)" select="
280280
map{

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/imports/default.xsl

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,6 @@ exclude-result-prefixes="#all"
248248
<xsl:function name="ldh:url-decode" as="xs:string" use-when="system-property('xsl:product-name') eq 'SaxonJS'">
249249
<xsl:param name="encoded-string" as="xs:string"/>
250250

251-
<xsl:message>
252-
$encoded-string: <xsl:value-of select="$encoded-string"/>
253-
</xsl:message>
254-
255251
<xsl:sequence select="ixsl:call(ixsl:window(), 'decodeURIComponent', [ $encoded-string ])"/>
256252
</xsl:function>
257253

@@ -266,10 +262,6 @@ exclude-result-prefixes="#all"
266262
<xsl:function name="ldh:parse-query-params" as="map(xs:string, xs:string*)">
267263
<xsl:param name="query-string" as="xs:string"/>
268264

269-
<xsl:message>
270-
$query-string: <xsl:value-of select="$query-string"/>
271-
</xsl:message>
272-
273265
<xsl:sequence select="map:merge(
274266
tokenize($query-string, '&amp;')[normalize-space()]
275267
!

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/client.xsl

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -889,34 +889,6 @@ WHERE
889889
</xsl:if>
890890

891891
<xsl:if test="$push-state">
892-
<!-- cannot use ixsl:query-params() because they're not up to date with $href at this point -->
893-
<!-- <xsl:variable name="query-params" select="if (contains($href, '?')) then ldh:parse-query-params(substring-after($href, '?')) else map{}" as="map(xs:string, xs:string*)"/>
894-
<xsl:variable name="nav-tab-class" select="id('content-body', ixsl:page())/div[contains-token(@class, 'row-fluid')][1]/ul[contains-token(@class, 'nav-tabs')]/li[contains-token(@class, 'active')]/@class" as="xs:string?"/>
895-
<xsl:variable name="href" as="xs:anyURI">
896-
<xsl:choose>
897-
if ?mode param is not explicitly specified, change the page's URL to reflect that (there's always an active mode)
898-
need to check .nav-tabs because they might not be rendered (e.g. in case of error response)
899-
<xsl:when test="not(exists($query-params?mode)) and $nav-tab-class">
900-
<xsl:variable name="mode-classes" as="map(xs:string, xs:string)">
901-
<xsl:map>
902-
<xsl:map-entry key="'content-mode'" select="'&ldh;ContentMode'"/>
903-
<xsl:map-entry key="'read-mode'" select="'&ac;ReadMode'"/>
904-
<xsl:map-entry key="'map-mode'" select="'&ac;MapMode'"/>
905-
<xsl:map-entry key="'chart-mode'" select="'&ac;ChartMode'"/>
906-
<xsl:map-entry key="'graph-mode'" select="'&ac;GraphMode'"/>
907-
</xsl:map>
908-
</xsl:variable>
909-
<xsl:variable name="mode-class" select="map:keys($mode-classes)[contains-token($nav-tab-class, .)]" as="xs:string"/>
910-
<xsl:variable name="mode" select="xs:anyURI(map:get($mode-classes, $mode-class))" as="xs:anyURI"/>
911-
<xsl:variable name="fragment" select="substring-after($href, '#')" as="xs:string"/>
912-
<xsl:sequence select="xs:anyURI(ldh:href(ac:build-uri($href, map:merge(($query-params, map{ 'mode': string($mode) } ))), map{}, $fragment))"/>
913-
</xsl:when>
914-
<xsl:otherwise>
915-
<xsl:sequence select="$href"/>
916-
</xsl:otherwise>
917-
</xsl:choose>
918-
</xsl:variable>-->
919-
920892
<xsl:call-template name="ldh:PushState">
921893
<xsl:with-param name="href" select="ldh:href($href, map{})"/>
922894
<xsl:with-param name="title" select="/html/head/title"/>

0 commit comments

Comments
 (0)