Skip to content

Commit 6e4983b

Browse files
authored
Uniform ldh:href function calls (#238)
* Refactoring the `ldh:href` function to make its calls uniform New `ldh:url-decode` XSLT extension function `graph` URL param deprecated * `ldh:parse-query-params` fix Don't redirect to `ldh:Content` mode (for now at least) * `JSONGRDDLFilter` concurrency fix
1 parent 6c133ce commit 6e4983b

24 files changed

Lines changed: 255 additions & 186 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ protected PasswordAuthentication getPasswordAuthentication()
747747

748748
xsltProc.registerExtensionFunction(new UUID());
749749
xsltProc.registerExtensionFunction(new DecodeURI());
750+
xsltProc.registerExtensionFunction(new com.atomgraph.linkeddatahub.writer.function.URLDecode());
750751
xsltProc.registerExtensionFunction(new com.atomgraph.linkeddatahub.writer.function.Construct(xsltProc));
751752
xsltProc.registerExtensionFunction(new com.atomgraph.linkeddatahub.writer.function.SendHTTPRequest(xsltProc, client));
752753

src/main/java/com/atomgraph/linkeddatahub/client/filter/JSONGRDDLFilter.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public JSONGRDDLFilter(XsltCompiler xsltCompiler, String stylesheetPath) throws
7676
if (log.isDebugEnabled()) log.debug("Compiled GRDDL stylesheet from {} for {}", stylesheetPath, getClass().getSimpleName());
7777
}
7878

79-
private URI originalRequestURI; // Store original URI for response processing
79+
private static final String ORIGINAL_URI_PROPERTY = "com.atomgraph.linkeddatahub.originalRequestURI";
8080

8181
@Override
8282
public void filter(ClientRequestContext requestContext) throws IOException
@@ -92,8 +92,8 @@ public void filter(ClientRequestContext requestContext) throws IOException
9292
if (jsonURI == null)
9393
return;
9494

95-
// Store original URI for response processing
96-
this.originalRequestURI = requestURI;
95+
// Store original URI in request context for thread-safe response processing
96+
requestContext.setProperty(ORIGINAL_URI_PROPERTY, requestURI);
9797

9898
// Redirect request to JSON API endpoint
9999
requestContext.setUri(jsonURI);
@@ -104,17 +104,17 @@ public void filter(ClientRequestContext requestContext) throws IOException
104104
@Override
105105
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException
106106
{
107+
// Get the original URI from request context
108+
URI originalRequestURI = (URI) requestContext.getProperty(ORIGINAL_URI_PROPERTY);
109+
107110
// Only process responses if we redirected the original request
108111
if (originalRequestURI == null)
109112
return;
110113

111114
// Check if response is JSON
112115
MediaType contentType = responseContext.getMediaType();
113116
if (contentType == null || !MediaType.APPLICATION_JSON_TYPE.isCompatible(contentType))
114-
{
115-
originalRequestURI = null; // Reset for next request
116117
return;
117-
}
118118

119119
try (InputStream entityStream = responseContext.getEntityStream())
120120
{
@@ -136,10 +136,6 @@ public void filter(ClientRequestContext requestContext, ClientResponseContext re
136136
if (log.isErrorEnabled()) log.error("GRDDL transformation failed for URI: {}", originalRequestURI, ex);
137137
throw new BadGatewayException("Failed to transform JSON to RDF", ex);
138138
}
139-
finally
140-
{
141-
originalRequestURI = null; // Reset for next request
142-
}
143139
}
144140

145141
/**

src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ApplicationFilter.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import com.atomgraph.client.vocabulary.AC;
2020
import com.atomgraph.linkeddatahub.vocabulary.LAPP;
21-
import com.atomgraph.linkeddatahub.vocabulary.LDH;
2221
import com.atomgraph.linkeddatahub.writer.Mode;
2322
import java.io.IOException;
2423
import java.util.Collections;
@@ -72,21 +71,20 @@ public void filter(ContainerRequestContext request) throws IOException
7271
com.atomgraph.linkeddatahub.apps.model.Application app = appResource.as(com.atomgraph.linkeddatahub.apps.model.Application.class);
7372
request.setProperty(LAPP.Application.getURI(), app); // wrap into a helper class so it doesn't interfere with injection of Application
7473

75-
// use the ?graph URL parameter to override the effective request URI if its URI value is relative to the app's base URI
74+
// use the ?uri URL parameter to override the effective request URI if its URI value is relative to the app's base URI
7675
final URI requestURI;
77-
if (request.getUriInfo().getQueryParameters().containsKey(LDH.graph.getLocalName()))
76+
if (request.getUriInfo().getQueryParameters().containsKey(AC.uri.getLocalName()))
7877
try
7978
{
80-
URI graphURI = new URI(request.getUriInfo().getQueryParameters().getFirst(LDH.graph.getLocalName()));
79+
URI graphURI = new URI(request.getUriInfo().getQueryParameters().getFirst(AC.uri.getLocalName()));
8180
if (!app.getBaseURI().relativize(graphURI).isAbsolute()) // if ?graph query param value is relative to the app's base URI
8281
{
8382
// pass on query parameters except ?graph
8483
MultivaluedMap<String, String> queryParams = new MultivaluedHashMap();
8584
queryParams.putAll(request.getUriInfo().getQueryParameters());
86-
queryParams.remove(LDH.graph.getLocalName());
8785
queryParams.remove(AC.uri.getLocalName());
8886

89-
UriBuilder builder = UriBuilder.fromUri(graphURI);;
87+
UriBuilder builder = UriBuilder.fromUri(graphURI);
9088

9189
for (Entry<String, List<String>> params : queryParams.entrySet())
9290
for (String value : params.getValue())

src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDH.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,6 @@ public static String getURI()
107107

108108
/**
109109
* Graph URI property */
110-
public static final ObjectProperty graph = m_model.createObjectProperty( NS + "graph" );
110+
//public static final ObjectProperty graph = m_model.createObjectProperty( NS + "graph" );
111111

112112
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Copyright 2025 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+
*/
17+
package com.atomgraph.linkeddatahub.writer.function;
18+
19+
import java.io.UnsupportedEncodingException;
20+
import java.net.URLDecoder;
21+
import net.sf.saxon.expr.XPathContext;
22+
import net.sf.saxon.lib.ExtensionFunctionCall;
23+
import net.sf.saxon.lib.ExtensionFunctionDefinition;
24+
import net.sf.saxon.om.Sequence;
25+
import net.sf.saxon.om.StructuredQName;
26+
import net.sf.saxon.trans.XPathException;
27+
import net.sf.saxon.value.SequenceType;
28+
import net.sf.saxon.value.StringValue;
29+
30+
/**
31+
* Saxon extension function that URL-decodes a string using java.net.URLDecoder.
32+
*
33+
* @author Martynas Jusevičius {@literal <martynas@atomgraph.com>}
34+
*/
35+
public class URLDecode extends ExtensionFunctionDefinition
36+
{
37+
38+
public static final String LOCAL_NAME = "url-decode";
39+
40+
@Override
41+
public StructuredQName getFunctionQName()
42+
{
43+
return new StructuredQName("ldh", "https://w3id.org/atomgraph/linkeddatahub#", LOCAL_NAME);
44+
}
45+
46+
@Override
47+
public int getMinimumNumberOfArguments()
48+
{
49+
return 1;
50+
}
51+
52+
@Override
53+
public int getMaximumNumberOfArguments()
54+
{
55+
return 2;
56+
}
57+
58+
@Override
59+
public SequenceType[] getArgumentTypes()
60+
{
61+
return new SequenceType[]
62+
{
63+
SequenceType.SINGLE_STRING, // encoded string
64+
SequenceType.OPTIONAL_STRING // encoding (optional, defaults to UTF-8)
65+
};
66+
}
67+
68+
@Override
69+
public SequenceType getResultType(SequenceType[] suppliedArgumentTypes)
70+
{
71+
return SequenceType.SINGLE_STRING;
72+
}
73+
74+
@Override
75+
public ExtensionFunctionCall makeCallExpression()
76+
{
77+
return new ExtensionFunctionCall()
78+
{
79+
80+
@Override
81+
public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException
82+
{
83+
String encodedString = arguments[0].head().getStringValue();
84+
String encoding = arguments.length > 1 && arguments[1].head() != null ?
85+
arguments[1].head().getStringValue() : "UTF-8";
86+
87+
try
88+
{
89+
// Handle + to space conversion for application/x-www-form-urlencoded format
90+
String plusDecoded = encodedString.replace("+", " ");
91+
String decoded = URLDecoder.decode(plusDecoded, encoding);
92+
return StringValue.makeStringValue(decoded);
93+
}
94+
catch (UnsupportedEncodingException ex)
95+
{
96+
throw new XPathException("Unsupported encoding: " + encoding, ex);
97+
}
98+
}
99+
100+
};
101+
}
102+
103+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ exclude-result-prefixes="#all">
6464

6565
<xsl:template match="*[rdf:type/@rdf:resource = '&lacl;AuthorizationRequest']" priority="1">
6666
<xsl:param name="method" select="'post'" as="xs:string"/>
67-
<xsl:param name="action" select="ldh:href($ldt:base, resolve-uri(ac:uuid() || '/', resolve-uri('acl/authorizations/', $ldt:base)), map{ '_method': 'PUT' })" as="xs:anyURI"/> <!-- create new authorization document -->
67+
<xsl:param name="action" select="ldh:href(resolve-uri(ac:uuid() || '/', resolve-uri('acl/authorizations/', $ldt:base)), map{ '_method': 'PUT' })" as="xs:anyURI"/> <!-- create new authorization document -->
6868
<xsl:param name="id" select="concat('form-', generate-id())" as="xs:string?"/>
6969
<xsl:param name="class" select="'form-horizontal'" as="xs:string?"/>
7070
<xsl:param name="accept-charset" select="'UTF-8'" as="xs:string?"/>

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/admin/layout.xsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ exclude-result-prefixes="#all">
103103
<xsl:param name="id" select="concat('form-', generate-id())" as="xs:string?"/>
104104
<xsl:param name="class" select="'row-fluid'" as="xs:string?"/>
105105
<xsl:param name="method" select="'patch'" as="xs:string"/>
106-
<xsl:param name="action" select="ldh:href($ldt:base, ac:absolute-path($ldh:requestUri), map{}, ac:build-uri(ac:absolute-path(ldh:base-uri(.)), map{ '_method': 'PUT', 'mode': for $mode in $ac:mode return string($mode) }))" as="xs:anyURI"/>
106+
<xsl:param name="action" select="ldh:href(ac:build-uri(ac:absolute-path(ldh:base-uri(.)), map{ '_method': 'PUT' }), map{ 'mode': for $mode in $ac:mode return string($mode) })" as="xs:anyURI"/>
107107
<xsl:param name="enctype" select="'multipart/form-data'" as="xs:string?"/>
108108
<xsl:param name="create-resource" select="true()" as="xs:boolean"/>
109109
<!-- TO-DO: generate ontology classes from the OWL vocabulary -->

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/admin/signup.xsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ exclude-result-prefixes="#all">
6767
<xsl:template match="*[*][@rdf:about or @rdf:nodeID][ac:absolute-path($ldh:requestUri) = resolve-uri(encode-for-uri('sign up'), $ldt:base)]" mode="bs2:Right" use-when="system-property('xsl:product-name') = 'SAXON'"/>
6868

6969
<xsl:template match="rdf:RDF[ac:absolute-path($ldh:requestUri) = resolve-uri(encode-for-uri('sign up'), $ldt:base)]" mode="bs2:Row" priority="2">
70-
<xsl:variable name="constructors" select="ldh:query-result(map{}, resolve-uri('ns', $ldt:base), $constructor-query || ' VALUES $Type { ' || string-join(for $type in '&foaf;Person' return '&lt;' || $type || '&gt;', ' ') || ' }')" as="document-node()?"/>
70+
<xsl:variable name="constructors" select="ldh:query-result(resolve-uri('ns', $ldt:base), $constructor-query || ' VALUES $Type { ' || string-join(for $type in '&foaf;Person' return '&lt;' || $type || '&gt;', ' ') || ' }')" as="document-node()?"/>
7171
<xsl:apply-templates select="ldh:construct(map{ xs:anyURI('&foaf;Person'): $constructors//srx:result[srx:binding[@name = 'Type'] = '&foaf;Person']/srx:binding[@name = 'construct']/srx:literal/string() })" mode="bs2:RowForm">
7272
<xsl:with-param name="id" select="'form-signup'"/>
7373
<xsl:with-param name="method" select="'post'"/> <!-- don't use PATCH which is the default -->

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ exclude-result-prefixes="#all"
235235
<xsl:variable name="block-uri" select="$block/@about" as="xs:anyURI"/>
236236
<xsl:variable name="update-string" select="replace($block-delete-string, '$this', '&lt;' || ac:absolute-path(ldh:base-uri(.)) || '&gt;', 'q')" as="xs:string"/>
237237
<xsl:variable name="update-string" select="replace($update-string, '$block', '&lt;' || $block-uri || '&gt;', 'q')" as="xs:string"/>
238-
<xsl:variable name="request-uri" select="ldh:href($ldt:base, ac:absolute-path($ldh:requestUri), map{}, ac:absolute-path(ldh:base-uri(.)))" as="xs:anyURI"/>
238+
<xsl:variable name="request-uri" select="ldh:href(ac:absolute-path(ldh:base-uri(.)), map{})" as="xs:anyURI"/>
239239
<xsl:variable name="request" as="item()*">
240240
<ixsl:schedule-action http-request="map{ 'method': 'PATCH', 'href': $request-uri, 'media-type': 'application/sparql-update', 'body': $update-string }">
241241
<xsl:call-template name="onBlockDelete">
@@ -316,7 +316,7 @@ exclude-result-prefixes="#all"
316316
<xsl:variable name="update-string" select="replace($block-swap-string, '$this', '&lt;' || ac:absolute-path(ldh:base-uri(.)) || '&gt;', 'q')" as="xs:string"/>
317317
<xsl:variable name="update-string" select="replace($update-string, '$targetBlock', '&lt;' || $block-uri || '&gt;', 'q')" as="xs:string"/>
318318
<xsl:variable name="update-string" select="replace($update-string, '$sourceBlock', '&lt;' || $drop-block-uri || '&gt;', 'q')" as="xs:string"/>
319-
<xsl:variable name="request-uri" select="ldh:href($ldt:base, ac:absolute-path($ldh:requestUri), map{}, ac:absolute-path(ldh:base-uri(.)))" as="xs:anyURI"/>
319+
<xsl:variable name="request-uri" select="ldh:href(ac:absolute-path(ldh:base-uri(.)), map{})" as="xs:anyURI"/>
320320
<xsl:variable name="request" as="item()*">
321321
<ixsl:schedule-action http-request="map{ 'method': 'PATCH', 'href': $request-uri, 'media-type': 'application/sparql-update', 'body': $update-string }">
322322
<xsl:call-template name="onBlockSwap"/>

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ exclude-result-prefixes="#all"
291291
<ixsl:set-style name="width" select="'66%'" object="."/>
292292
</xsl:for-each>
293293

294-
<xsl:variable name="request-uri" select="ldh:href($ldt:base, ac:absolute-path($ldh:requestUri), map{}, $query-uri)" as="xs:anyURI"/>
294+
<xsl:variable name="request-uri" select="ldh:href($query-uri, map{})" as="xs:anyURI"/>
295295
<xsl:variable name="request" select="map{ 'method': 'GET', 'href': $request-uri, 'headers': map{ 'Accept': 'application/rdf+xml' } }" as="map(*)"/>
296296
<xsl:variable name="context" as="map(*)" select="
297297
map{
@@ -658,7 +658,7 @@ exclude-result-prefixes="#all"
658658
<xsl:apply-templates select="$constructed-doc" mode="bs2:RowForm">
659659
<xsl:with-param name="about" select="()"/> <!-- don't set @about on the container until after the resource is saved -->
660660
<xsl:with-param name="method" select="$method"/>
661-
<xsl:with-param name="action" select="ldh:href($ldt:base, ac:absolute-path($ldh:requestUri), map{}, $doc-uri)" as="xs:anyURI"/>
661+
<xsl:with-param name="action" select="ldh:href($doc-uri, map{})" as="xs:anyURI"/>
662662
<xsl:with-param name="type-metadata" select="$type-metadata" tunnel="yes"/>
663663
<xsl:with-param name="property-metadata" select="$property-metadata" tunnel="yes"/>
664664
<xsl:with-param name="constructor" select="$constructed-doc" tunnel="yes"/>
@@ -722,7 +722,7 @@ exclude-result-prefixes="#all"
722722
</rdf:RDF>
723723
</xsl:document>
724724
</xsl:variable>
725-
<xsl:variable name="request-uri" select="ldh:href($ldt:base, ac:absolute-path($ldh:requestUri), map{}, $action)" as="xs:anyURI"/>
725+
<xsl:variable name="request-uri" select="ldh:href($action, map{})" as="xs:anyURI"/>
726726
<!-- If-Match header checks preconditions, i.e. that the graph has not been modified in the meanwhile -->
727727
<xsl:variable name="request" select="map{ 'method': $method, 'href': $request-uri, 'media-type': 'application/sparql-update', 'body': $update-string, 'headers': map{ 'If-Match': $etag, 'Accept': 'application/rdf+xml', 'Cache-Control': 'no-cache' } }" as="map(*)"/>
728728
<xsl:variable name="context" as="map(*)" select="
@@ -772,7 +772,7 @@ exclude-result-prefixes="#all"
772772
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ac:build-uri(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
773773
<xsl:variable name="endpoint" select="($service/sd:endpoint/@rdf:resource/xs:anyURI(.), sd:endpoint())[1]" as="xs:anyURI"/>
774774
<xsl:variable name="results-uri" select="ac:build-uri($endpoint, map{ 'query': $query-string })" as="xs:anyURI"/>
775-
<xsl:variable name="request-uri" select="ldh:href($ldt:base, ac:absolute-path($ldh:requestUri), map{}, $results-uri)" as="xs:anyURI"/>
775+
<xsl:variable name="request-uri" select="ldh:href($results-uri, map{})" as="xs:anyURI"/>
776776

777777
<!-- update progress bar -->
778778
<xsl:for-each select="$block//div[contains-token(@class, 'bar')]">

0 commit comments

Comments
 (0)