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

knowledgepixels / nanopub-query / 17945178198

23 Sep 2025 11:47AM UTC coverage: 70.508% (+0.06%) from 70.444%
17945178198

push

github

tkuhn
feat: Fully remove third-party grlc service

206 of 318 branches covered (64.78%)

Branch coverage included in aggregate %.

571 of 784 relevant lines covered (72.83%)

3.66 hits per line

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

60.35
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.MalformedQueryException;
17
import org.eclipse.rdf4j.query.algebra.Var;
18
import org.eclipse.rdf4j.query.algebra.helpers.AbstractSimpleQueryModelVisitor;
19
import org.eclipse.rdf4j.query.parser.ParsedQuery;
20
import org.eclipse.rdf4j.query.parser.sparql.SPARQLParser;
21
import org.nanopub.Nanopub;
22
import org.nanopub.SimpleCreatorPattern;
23
import org.nanopub.extra.server.GetNanopub;
24
import org.nanopub.extra.services.QueryAccess;
25

26
import io.vertx.core.MultiMap;
27
import net.trustyuri.TrustyUriUtils;
28
import org.slf4j.Logger;
29
import org.slf4j.LoggerFactory;
30

31
//TODO merge this class with GrlcQuery of Nanodash and move to a library like nanopub-java
32
/**
33
 * This class produces a page with the grlc specification. This is needed internally to tell grlc
34
 * how to execute a particular query template.
35
 */
36
public class GrlcSpec {
37

38
    private static final ValueFactory vf = SimpleValueFactory.getInstance();
2✔
39

40
    private static final Logger log = LoggerFactory.getLogger(GrlcSpec.class);
3✔
41

42
    public static class InvalidGrlcSpecException extends Exception {
43

44
        private InvalidGrlcSpecException(String msg) {
45
            super(msg);
3✔
46
        }
1✔
47

48
        private InvalidGrlcSpecException(String msg, Throwable throwable) {
49
            super(msg, throwable);
×
50
        }
×
51

52
    }
53

54
    /**
55
     * IRI for relation to link a grlc query instance to its SPARQL template.
56
     */
57
    public static final IRI HAS_SPARQL = vf.createIRI("https://w3id.org/kpxl/grlc/sparql");
4✔
58

59
    /**
60
     * IRI for relation to link a grlc query instance to its SPARQL endpoint URL.
61
     */
62
    public static final IRI HAS_ENDPOINT = vf.createIRI("https://w3id.org/kpxl/grlc/endpoint");
4✔
63

64
    /**
65
     * URL for the given Nanopub Query instance, needed for internal coordination.
66
     */
67
    public static final String nanopubQueryUrl = Utils.getEnvString("NANOPUB_QUERY_URL", "http://query:9393/");
5✔
68

69
    private MultiMap parameters;
70
    private Nanopub np;
71
    private String requestUrlBase;
72
    private String artifactCode;
73
    private String queryPart;
74
    private String queryName;
75
    private String label;
76
    private String desc;
77
    private String license;
78
    private String queryContent;
79
    private String endpoint;
80
    private List<String> placeholdersList;
81

82
    /**
83
     * Creates a new page instance.
84
     *
85
     * @param requestUrl The request URL
86
     * @param parameters The URL request parameters
87
     */
88
    public GrlcSpec(String requestUrl, MultiMap parameters) throws InvalidGrlcSpecException {
2✔
89
        this.parameters = parameters;
3✔
90
        requestUrl = requestUrl.replaceFirst("\\?.*$", "");
5✔
91
        if (!requestUrl.matches(".*/RA[A-Za-z0-9\\-_]{43}/(.*)?")) {
4✔
92
            throw new InvalidGrlcSpecException("Invalid grlc API request: " + requestUrl);
6✔
93
        }
94
        artifactCode = requestUrl.replaceFirst("^(.*/)(RA[A-Za-z0-9\\-_]{43})/(.*)?$", "$2");
6✔
95
        requestUrlBase = requestUrl.replaceFirst("^/(.*/)(RA[A-Za-z0-9\\-_]{43})/(.*)?$", "$1");
6✔
96

97
        queryPart = requestUrl.replaceFirst("^(.*/)(RA[A-Za-z0-9\\-_]{43}/)(.*)?$", "$3");
6✔
98
        queryPart = queryPart.replaceFirst(".rq$", "");
7✔
99

100
        // TODO Get the nanopub from the local store:
101
        np = GetNanopub.get(artifactCode);
5✔
102
        if (parameters.get("api-version") != null && parameters.get("api-version").equals("latest")) {
10✔
103
            // TODO Get the latest version from the local store:
104
            np = GetNanopub.get(QueryAccess.getLatestVersionId(np.getUri().stringValue()));
8✔
105
            artifactCode = TrustyUriUtils.getArtifactCode(np.getUri().stringValue());
7✔
106
        }
107
        for (Statement st : np.getAssertion()) {
12✔
108
            if (!st.getSubject().stringValue().startsWith(np.getUri().stringValue())) continue;
9!
109
            String qn = st.getSubject().stringValue().replaceFirst("^.*[#/](.*)$", "$1");
7✔
110
            if (queryName != null && !qn.equals(queryName)) {
8!
111
                throw new InvalidGrlcSpecException("Subject suffixes don't match: " + queryName);
×
112
            }
113
            queryName = qn;
3✔
114
            if (st.getPredicate().equals(RDFS.LABEL)) {
5✔
115
                label = st.getObject().stringValue();
6✔
116
            } else if (st.getPredicate().equals(DCTERMS.DESCRIPTION)) {
5✔
117
                desc = st.getObject().stringValue();
6✔
118
            } else if (st.getPredicate().equals(DCTERMS.LICENSE) && st.getObject() instanceof IRI) {
9!
119
                license = st.getObject().stringValue();
6✔
120
            } else if (st.getPredicate().equals(HAS_SPARQL)) {
5✔
121
                // TODO Improve this:
122
                queryContent = st.getObject().stringValue().replace("https://w3id.org/np/l/nanopub-query-1.1/repo/", nanopubQueryUrl + "repo/");
10✔
123
            } else if (st.getPredicate().equals(HAS_ENDPOINT) && st.getObject() instanceof IRI) {
9!
124
                endpoint = st.getObject().stringValue();
5✔
125
                if (endpoint.startsWith("https://w3id.org/np/l/nanopub-query-1.1/repo/")) {
5!
126
                    endpoint = endpoint.replace("https://w3id.org/np/l/nanopub-query-1.1/repo/", nanopubQueryUrl + "repo/");
9✔
127
                } else {
128
                    throw new InvalidGrlcSpecException("Invalid/non-recognized endpoint: " + endpoint);
×
129
                }
130
            }
131
        }
1✔
132

133
        if (!queryPart.isEmpty() && !queryPart.equals(queryName)) {
10✔
134
            throw new InvalidGrlcSpecException("Query part doesn't match query name: " + queryPart + " / " + queryName);
9✔
135
        }
136

137
        final Set<String> placeholders = new HashSet<>();
4✔
138
        try {
139
            ParsedQuery query = new SPARQLParser().parseQuery(queryContent, null);
8✔
140
            query.getTupleExpr().visitChildren(new AbstractSimpleQueryModelVisitor<>() {
14✔
141
        
142
                @Override
143
                public void meet(Var node) throws RuntimeException {
144
                    super.meet(node);
3✔
145
                    if (!node.isConstant() && !node.isAnonymous() && node.getName().startsWith("_")) {
11!
146
                        placeholders.add(node.getName());
×
147
                    }
148
                }
1✔
149
        
150
            });
151
        } catch (MalformedQueryException ex) {
×
152
            throw new InvalidGrlcSpecException("Invalid SPARQL string", ex);
×
153
        }
1✔
154
        List<String> placeholdersListPre = new ArrayList<>(placeholders);
5✔
155
        Collections.sort(placeholdersListPre);
2✔
156
        placeholdersListPre.sort(Comparator.comparing(String::length));
4✔
157
        placeholdersList = Collections.unmodifiableList(placeholdersListPre);
4✔
158
    }
1✔
159

160
    /**
161
     * Returns the grlc spec as a string.
162
     *
163
     * @return grlc specification string
164
     */
165
    public String getSpec() {
166
        String s = "";
2✔
167
        if (queryPart.isEmpty()) {
4✔
168
            if (label == null) {
3!
169
                s += "title: \"untitled query\"\n";
×
170
            } else {
171
                s += "title: \"" + escapeLiteral(label) + "\"\n";
6✔
172
            }
173
            s += "description: \"" + escapeLiteral(desc) + "\"\n";
6✔
174
            StringBuilder userName = new StringBuilder();
4✔
175
            Set<IRI> creators = SimpleCreatorPattern.getCreators(np);
4✔
176
            for (IRI userIri : creators) {
10✔
177
                userName.append(", ").append(userIri);
6✔
178
            }
1✔
179
            if (!userName.isEmpty()) userName = new StringBuilder(userName.substring(2));
10!
180
            String url = "";
2✔
181
            if (!creators.isEmpty()) url = creators.iterator().next().stringValue();
9!
182
            s += "contact:\n";
3✔
183
            s += "  name: \"" + escapeLiteral(userName.toString()) + "\"\n";
6✔
184
            s += "  url: " + url + "\n";
4✔
185
            if (license != null) {
3!
186
                s += "licence: " + license + "\n";
5✔
187
            }
188
            s += "queries:\n";
3✔
189
            s += "  - " + nanopubQueryUrl + requestUrlBase + artifactCode + "/" + queryName + ".rq";
10✔
190
        } else if (queryPart.equals(queryName)) {
7!
191
            if (label != null) {
3!
192
                s += "#+ summary: \"" + escapeLiteral(label) + "\"\n";
6✔
193
            }
194
            if (desc != null) {
3!
195
                s += "#+ description: \"" + escapeLiteral(desc) + "\"\n";
6✔
196
            }
197
            if (license != null) {
3!
198
                s += "#+ licence: " + license + "\n";
5✔
199
            }
200
            if (endpoint != null) {
3!
201
                s += "#+ endpoint: " + endpoint + "\n";
5✔
202
            }
203
            s += "\n";
3✔
204
            s += queryContent;
6✔
205
        } else {
206
            throw new RuntimeException("Unexpected queryPart: " + queryPart);
×
207
        }
208
        return s;
2✔
209
    }
210

211
    public MultiMap getParameters() {
212
        return parameters;
×
213
    }
214

215
    public Nanopub getNanopub() {
216
        return np;
3✔
217
    }
218

219
    public String getArtifactCode() {
220
        return artifactCode;
3✔
221
    }
222

223
    public String getLabel() {
224
        return label;
3✔
225
    }
226

227
    public String getDescription() {
228
        return desc;
3✔
229
    }
230

231
    public String getQueryName() {
232
        return queryName;
3✔
233
    }
234

235
    public List<String> getPlaceholdersList() {
236
        return placeholdersList;
3✔
237
    }
238

239
    public String getRepoName() {
240
        return endpoint.replaceAll("/", "_").replaceFirst("^.*_repo_", "");
×
241
    }
242

243
    public String getQueryContent() {
244
        return queryContent;
×
245
    }
246

247
    public String expandQuery() throws InvalidGrlcSpecException {
248
        String expandedQueryContent = queryContent;
×
249
        for (String ph : placeholdersList) {
×
250
            log.info("ph: ", ph);
×
251
            log.info("getParamName(ph): ", getParamName(ph));
×
252
            if (isMultiPlaceholder(ph)) {
×
253
                // TODO multi placeholders need proper documentation
254
                List<String> val = parameters.getAll(getParamName(ph));
×
255
                if (!isOptionalPlaceholder(ph) && val.isEmpty()) {
×
256
                    throw new InvalidGrlcSpecException("Missing value for non-optional placeholder: " + ph);
×
257
                }
258
                if (val.isEmpty()) {
×
259
                    expandedQueryContent = expandedQueryContent.replaceAll("values\\s*\\?" + ph + "\\s*\\{\\s*\\}(\\s*\\.)?", "");
×
260
                    continue;
×
261
                }
262
                String valueList = "";
×
263
                for (String v : val) {
×
264
                    if (isIriPlaceholder(ph)) {
×
265
                        valueList += serializeIri(v) + " ";
×
266
                    } else {
267
                        valueList += serializeLiteral(v) + " ";
×
268
                    }
269
                }
×
270
                expandedQueryContent = expandedQueryContent.replaceAll("values\\s*\\?" + ph + "\\s*\\{\\s*\\}", "values ?" + ph + " { " + valueList + "}");
×
271
            } else {
×
272
                String val = parameters.get(getParamName(ph));
×
273
                log.info("val: ", val);
×
274
                if (!isOptionalPlaceholder(ph) && val == null) {
×
275
                    throw new InvalidGrlcSpecException("Missing value for non-optional placeholder: " + ph);
×
276
                }
277
                if (val == null) continue;
×
278
                if (isIriPlaceholder(ph)) {
×
279
                    expandedQueryContent = expandedQueryContent.replaceAll("\\?" + ph, serializeIri(val));
×
280
                } else {
281
                    expandedQueryContent = expandedQueryContent.replaceAll("\\?" + ph, serializeLiteral(val));
×
282
                }
283
            }
284
        }
×
285
        log.info("Expanded grlc query:\n", expandedQueryContent);
×
286
        return expandedQueryContent;
×
287
    }
288

289
    public static String escapeLiteral(String s) {
290
        return s.replace("\\", "\\\\").replace("\n", "\\n").replace("\"", "\\\"");
11✔
291
    }
292

293
    public static boolean isOptionalPlaceholder(String placeholder) {
294
        return placeholder.startsWith("__");
×
295
    }
296

297
    public static boolean isMultiPlaceholder(String placeholder) {
298
        return placeholder.endsWith("_multi") || placeholder.endsWith("_multi_iri");
×
299
    }
300

301
    public static boolean isIriPlaceholder(String placeholder) {
302
        return placeholder.endsWith("_iri");
×
303
    }
304

305
    public static String getParamName(String placeholder) {
306
        return placeholder.replaceFirst("^_+", "").replaceFirst("_iri$", "").replaceFirst("_multi$", "");
×
307
    }
308

309
    public static String serializeIri(String iriString) {
310
        return "<" + iriString + ">";
×
311
    }
312

313
    public static String serializeLiteral(String literalString) {
314
        return "\"" + escapeLiteral(literalString) + "\"";
×
315
    }
316

317
}
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

© 2025 Coveralls, Inc