• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanopub-query / 17764137924

16 Sep 2025 11:22AM UTC coverage: 71.741% (-2.2%) from 73.984%
17764137924

push

github

tkuhn
Add support for multi-value grlc placeholders (on separate /apix/ path)

234 of 350 branches covered (66.86%)

Branch coverage included in aggregate %.

586 of 793 relevant lines covered (73.9%)

3.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.81
src/main/java/com/knowledgepixels/query/GrlcSpec.java
1
package com.knowledgepixels.query;
2

3
import java.util.ArrayList;
4
import java.util.Collections;
5
import java.util.Comparator;
6
import java.util.HashSet;
7
import java.util.List;
8
import java.util.Set;
9

10
import org.eclipse.rdf4j.model.IRI;
11
import org.eclipse.rdf4j.model.Statement;
12
import org.eclipse.rdf4j.model.ValueFactory;
13
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
14
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
15
import org.eclipse.rdf4j.model.vocabulary.RDFS;
16
import org.eclipse.rdf4j.query.algebra.Var;
17
import org.eclipse.rdf4j.query.algebra.helpers.AbstractSimpleQueryModelVisitor;
18
import org.eclipse.rdf4j.query.parser.ParsedQuery;
19
import org.eclipse.rdf4j.query.parser.sparql.SPARQLParser;
20
import org.nanopub.Nanopub;
21
import org.nanopub.SimpleCreatorPattern;
22
import org.nanopub.extra.server.GetNanopub;
23
import org.nanopub.extra.services.QueryAccess;
24

25
import io.vertx.core.MultiMap;
26
import net.trustyuri.TrustyUriUtils;
27

28
/**
29
 * This class produces a page with the grlc specification. This is needed internally to tell grlc
30
 * how to execute a particular query template.
31
 */
32
public class GrlcSpec {
33

34
    private static final ValueFactory vf = SimpleValueFactory.getInstance();
2✔
35

36
    /**
37
     * IRI for relation to link a grlc query instance to its SPARQL template.
38
     */
39
    public static final IRI HAS_SPARQL = vf.createIRI("https://w3id.org/kpxl/grlc/sparql");
4✔
40

41
    /**
42
     * IRI for relation to link a grlc query instance to its SPARQL endpoint URL.
43
     */
44
    public static final IRI HAS_ENDPOINT = vf.createIRI("https://w3id.org/kpxl/grlc/endpoint");
4✔
45

46
    /**
47
     * URL for the given Nanopub Query instance, needed for internal coordination.
48
     */
49
    public static final String nanopubQueryUrl = Utils.getEnvString("NANOPUB_QUERY_URL", "http://query:9393/");
5✔
50

51
    private MultiMap parameters;
52
    private Nanopub np;
53
    private String requestUrlBase;
54
    private String artifactCode, queryPart;
55
    private String queryName;
56
    private String label;
57
    private String desc;
58
    private String license;
59
    private String queryContent;
60
    private String endpoint;
61
    private List<String> placeholdersList;
62

63
    /**
64
     * Creates a new page instance.
65
     *
66
     * @param requestUrl The request URL
67
     * @param parameters The URL request parameters
68
     */
69
    public GrlcSpec(String requestUrl, MultiMap parameters) {
2✔
70
        requestUrl = requestUrl.replaceFirst("\\?.*$", "");
5✔
71
        this.parameters = parameters;
3✔
72
        if (!requestUrl.matches(".*/RA[A-Za-z0-9\\-_]{43}/(.*)?")) {
4✔
73
            // TODO Raise exception
74
            return;
1✔
75
        }
76
        artifactCode = requestUrl.replaceFirst("^(.*/)(RA[A-Za-z0-9\\-_]{43})/(.*)?$", "$2");
6✔
77
        queryPart = requestUrl.replaceFirst("^(.*/)(RA[A-Za-z0-9\\-_]{43}/)(.*)?$", "$3");
6✔
78
        requestUrlBase = requestUrl.replaceFirst("^/(.*/)(RA[A-Za-z0-9\\-_]{43})/(.*)?$", "$1");
6✔
79

80
        // TODO Get the nanopub from the local store:
81
        np = GetNanopub.get(artifactCode);
5✔
82
        if (parameters.get("api-version") != null && parameters.get("api-version").equals("latest")) {
10✔
83
            // TODO Get the latest version from the local store:
84
            np = GetNanopub.get(QueryAccess.getLatestVersionId(np.getUri().stringValue()));
8✔
85
            artifactCode = TrustyUriUtils.getArtifactCode(np.getUri().stringValue());
7✔
86
        }
87
        for (Statement st : np.getAssertion()) {
12✔
88
            if (!st.getSubject().stringValue().startsWith(np.getUri().stringValue())) continue;
9!
89
            String qn = st.getSubject().stringValue().replaceFirst("^.*[#/](.*)$", "$1");
7✔
90
            if (queryName != null && !qn.equals(queryName)) {
8!
91
                np = null;
×
92
                break;
×
93
            }
94
            queryName = qn;
3✔
95
            if (st.getPredicate().equals(RDFS.LABEL)) {
5✔
96
                label = st.getObject().stringValue();
6✔
97
            } else if (st.getPredicate().equals(DCTERMS.DESCRIPTION)) {
5✔
98
                desc = st.getObject().stringValue();
6✔
99
            } else if (st.getPredicate().equals(DCTERMS.LICENSE) && st.getObject() instanceof IRI) {
9!
100
                license = st.getObject().stringValue();
6✔
101
            } else if (st.getPredicate().equals(HAS_SPARQL)) {
5✔
102
                // TODO Improve this:
103
                queryContent = st.getObject().stringValue().replace("https://w3id.org/np/l/nanopub-query-1.1/repo/", nanopubQueryUrl + "repo/");
10✔
104
            } else if (st.getPredicate().equals(HAS_ENDPOINT) && st.getObject() instanceof IRI) {
9!
105
                endpoint = st.getObject().stringValue();
5✔
106
                if (endpoint.startsWith("https://w3id.org/np/l/nanopub-query-1.1/repo/")) {
5!
107
                    endpoint = endpoint.replace("https://w3id.org/np/l/nanopub-query-1.1/repo/", nanopubQueryUrl + "repo/");
9✔
108
                } else {
109
                    // TODO Raise exception
110
                    endpoint = null;
×
111
                }
112
            }
113
        }
1✔
114

115
        final Set<String> placeholders = new HashSet<>();
4✔
116
        ParsedQuery query = new SPARQLParser().parseQuery(queryContent, null);
8✔
117
        query.getTupleExpr().visitChildren(new AbstractSimpleQueryModelVisitor<>() {
14✔
118

119
            @Override
120
            public void meet(Var node) throws RuntimeException {
121
                super.meet(node);
3✔
122
                if (!node.isConstant() && !node.isAnonymous() && node.getName().startsWith("_")) {
11!
123
                    placeholders.add(node.getName());
×
124
                }
125
            }
1✔
126

127
        });
128
        List<String> placeholdersListPre = new ArrayList<>(placeholders);
5✔
129
        Collections.sort(placeholdersListPre);
2✔
130
        placeholdersListPre.sort(Comparator.comparing(String::length));
4✔
131
        placeholdersList = Collections.unmodifiableList(placeholdersListPre);
4✔
132
    }
1✔
133

134
    /**
135
     * Returns the grlc spec as a string.
136
     *
137
     * @return grlc specification string
138
     */
139
    public String getSpec() {
140
        if (np == null) return null;
5✔
141
        String s = "";
2✔
142
        if (queryPart.isEmpty()) {
4✔
143
            if (label == null) {
3!
144
                s += "title: \"untitled query\"\n";
×
145
            } else {
146
                s += "title: \"" + escapeLiteral(label) + "\"\n";
6✔
147
            }
148
            s += "description: \"" + escapeLiteral(desc) + "\"\n";
6✔
149
            StringBuilder userName = new StringBuilder();
4✔
150
            Set<IRI> creators = SimpleCreatorPattern.getCreators(np);
4✔
151
            for (IRI userIri : creators) {
10✔
152
                userName.append(", ").append(userIri);
6✔
153
            }
1✔
154
            if (!userName.isEmpty()) userName = new StringBuilder(userName.substring(2));
10!
155
            String url = "";
2✔
156
            if (!creators.isEmpty()) url = creators.iterator().next().stringValue();
9!
157
            s += "contact:\n";
3✔
158
            s += "  name: \"" + escapeLiteral(userName.toString()) + "\"\n";
6✔
159
            s += "  url: " + url + "\n";
4✔
160
            if (license != null) {
3!
161
                s += "licence: " + license + "\n";
5✔
162
            }
163
            s += "queries:\n";
3✔
164
            s += "  - " + nanopubQueryUrl + requestUrlBase + artifactCode + "/" + queryName + ".rq";
10✔
165
        } else if (queryPart.equals(queryName + ".rq")) {
8✔
166
            if (label != null) {
3!
167
                s += "#+ summary: \"" + escapeLiteral(label) + "\"\n";
6✔
168
            }
169
            if (desc != null) {
3!
170
                s += "#+ description: \"" + escapeLiteral(desc) + "\"\n";
6✔
171
            }
172
            if (license != null) {
3!
173
                s += "#+ licence: " + license + "\n";
5✔
174
            }
175
            if (endpoint != null) {
3!
176
                s += "#+ endpoint: " + endpoint + "\n";
5✔
177
            }
178
            s += "\n";
3✔
179
            s += queryContent;
6✔
180
        } else {
181
            return null;
2✔
182
        }
183
        return s;
2✔
184
    }
185

186
    public String getRepoName() {
187
        return endpoint.replaceAll("/", "_").replaceFirst("^.*_repo_", "");
×
188
    }
189

190
    public String getQueryContent() {
191
        return queryContent;
×
192
    }
193

194
    public String getExpandedQueryContent() {
195
        String expanded = queryContent;
×
196
        for (String ph : placeholdersList) {
×
197
            System.err.println("ph: " + ph);
×
198
            System.err.println("getParamName(ph): " + getParamName(ph));
×
199
            if (isMultiPlaceholder(ph)) {
×
200
                // TODO multi placeholders need proper documentation
201
                List<String> val = parameters.getAll(getParamName(ph));
×
202
                if (!isOptionalPlaceholder(ph) && val.isEmpty()) {
×
203
                    // TODO throw exception
204
                    return null;
×
205
                }
206
                if (val.isEmpty()) {
×
207
                    expanded = expanded.replaceAll("values\\s*\\?" + ph + "\\s*\\{\\s*\\}(\\s*\\.)?", "");
×
208
                    continue;
×
209
                }
210
                String valueList = "";
×
211
                for (String v : val) {
×
212
                    if (isIriPlaceholder(ph)) {
×
213
                        valueList += serializeIri(v) + " ";
×
214
                    } else {
215
                        valueList += serializeLiteral(v) + " ";
×
216
                    }
217
                }
×
218
                expanded = expanded.replaceAll("values\\s*\\?" + ph + "\\s*\\{\\s*\\}", "values ?" + ph + " { " + valueList + "}");
×
219
            } else {
×
220
                String val = parameters.get(getParamName(ph));
×
221
                System.err.println("val: " + val);
×
222
                if (!isOptionalPlaceholder(ph) && val == null) {
×
223
                    // TODO throw exception
224
                    return null;
×
225
                }
226
                if (val == null) continue;
×
227
                if (isIriPlaceholder(ph)) {
×
228
                    expanded = expanded.replaceAll("\\?" + ph, serializeIri(val));
×
229
                } else {
230
                    expanded = expanded.replaceAll("\\?" + ph, serializeLiteral(val));
×
231
                }
232
            }
233
        }
×
234
        System.err.println("expanded: " + expanded);
×
235
        return expanded;
×
236
    }
237

238
    public static String escapeLiteral(String s) {
239
        return s.replace("\\", "\\\\").replace("\n", "\\n").replace("\"", "\\\"");
11✔
240
    }
241

242
    public static boolean isOptionalPlaceholder(String placeholder) {
243
        return placeholder.startsWith("__");
×
244
    }
245

246
    public static boolean isMultiPlaceholder(String placeholder) {
247
        return placeholder.endsWith("_multi") || placeholder.endsWith("_multi_iri");
×
248
    }
249

250
    public static boolean isIriPlaceholder(String placeholder) {
251
        return placeholder.endsWith("_iri");
×
252
    }
253

254
    public static String getParamName(String placeholder) {
255
        return placeholder.replaceFirst("^_+", "").replaceFirst("_iri$", "").replaceFirst("_multi$", "");
×
256
    }
257

258
    public static String serializeIri(String iriString) {
259
        return "<" + iriString + ">";
×
260
    }
261

262
    public static String serializeLiteral(String literalString) {
263
        return "\"" + escapeLiteral(literalString) + "\"";
×
264
    }
265

266
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc