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

knowledgepixels / nanopub-query / 17771056034

16 Sep 2025 03:33PM UTC coverage: 71.466% (-0.1%) from 71.59%
17771056034

push

github

tkuhn
feat: Better handle grlc validation errors with InvalidGrlcSpecException

233 of 350 branches covered (66.57%)

Branch coverage included in aggregate %.

591 of 803 relevant lines covered (73.6%)

3.71 hits per line

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

59.43
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
import org.slf4j.Logger;
28
import org.slf4j.LoggerFactory;
29

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

36
    private static final ValueFactory vf = SimpleValueFactory.getInstance();
2✔
37

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

40
    public static class InvalidGrlcSpecException extends Exception {
41

42
        private InvalidGrlcSpecException(String msg) {
43
            super(msg);
×
44
        }
×
45

46
    }
47

48
    /**
49
     * IRI for relation to link a grlc query instance to its SPARQL template.
50
     */
51
    public static final IRI HAS_SPARQL = vf.createIRI("https://w3id.org/kpxl/grlc/sparql");
4✔
52

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

58
    /**
59
     * URL for the given Nanopub Query instance, needed for internal coordination.
60
     */
61
    public static final String nanopubQueryUrl = Utils.getEnvString("NANOPUB_QUERY_URL", "http://query:9393/");
5✔
62

63
    private MultiMap parameters;
64
    private Nanopub np;
65
    private String requestUrlBase;
66
    private String artifactCode, queryPart;
67
    private String queryName;
68
    private String label;
69
    private String desc;
70
    private String license;
71
    private String queryContent;
72
    private String endpoint;
73
    private List<String> placeholdersList;
74

75
    /**
76
     * Creates a new page instance.
77
     *
78
     * @param requestUrl The request URL
79
     * @param parameters The URL request parameters
80
     */
81
    public GrlcSpec(String requestUrl, MultiMap parameters) throws InvalidGrlcSpecException {
2✔
82
        requestUrl = requestUrl.replaceFirst("\\?.*$", "");
5✔
83
        this.parameters = parameters;
3✔
84
        if (!requestUrl.matches(".*/RA[A-Za-z0-9\\-_]{43}/(.*)?")) {
4✔
85
            // TODO Raise exception
86
            return;
1✔
87
        }
88
        artifactCode = requestUrl.replaceFirst("^(.*/)(RA[A-Za-z0-9\\-_]{43})/(.*)?$", "$2");
6✔
89
        queryPart = requestUrl.replaceFirst("^(.*/)(RA[A-Za-z0-9\\-_]{43}/)(.*)?$", "$3");
6✔
90
        requestUrlBase = requestUrl.replaceFirst("^/(.*/)(RA[A-Za-z0-9\\-_]{43})/(.*)?$", "$1");
6✔
91

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

126
        final Set<String> placeholders = new HashSet<>();
4✔
127
        // TODO Make sure we properly handle MalformedQueryException thrown here:
128
        ParsedQuery query = new SPARQLParser().parseQuery(queryContent, null);
8✔
129
        query.getTupleExpr().visitChildren(new AbstractSimpleQueryModelVisitor<>() {
14✔
130

131
            @Override
132
            public void meet(Var node) throws RuntimeException {
133
                super.meet(node);
3✔
134
                if (!node.isConstant() && !node.isAnonymous() && node.getName().startsWith("_")) {
11!
135
                    placeholders.add(node.getName());
×
136
                }
137
            }
1✔
138

139
        });
140
        List<String> placeholdersListPre = new ArrayList<>(placeholders);
5✔
141
        Collections.sort(placeholdersListPre);
2✔
142
        placeholdersListPre.sort(Comparator.comparing(String::length));
4✔
143
        placeholdersList = Collections.unmodifiableList(placeholdersListPre);
4✔
144
    }
1✔
145

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

198
    public String getRepoName() {
199
        return endpoint.replaceAll("/", "_").replaceFirst("^.*_repo_", "");
×
200
    }
201

202
    public String getQueryContent() {
203
        return queryContent;
×
204
    }
205

206
    public String getExpandedQueryContent() {
207
        String expanded = queryContent;
×
208
        for (String ph : placeholdersList) {
×
209
            log.info("ph: ", ph);
×
210
            log.info("getParamName(ph): ", getParamName(ph));
×
211
            if (isMultiPlaceholder(ph)) {
×
212
                // TODO multi placeholders need proper documentation
213
                List<String> val = parameters.getAll(getParamName(ph));
×
214
                if (!isOptionalPlaceholder(ph) && val.isEmpty()) {
×
215
                    // TODO throw exception
216
                    return null;
×
217
                }
218
                if (val.isEmpty()) {
×
219
                    expanded = expanded.replaceAll("values\\s*\\?" + ph + "\\s*\\{\\s*\\}(\\s*\\.)?", "");
×
220
                    continue;
×
221
                }
222
                String valueList = "";
×
223
                for (String v : val) {
×
224
                    if (isIriPlaceholder(ph)) {
×
225
                        valueList += serializeIri(v) + " ";
×
226
                    } else {
227
                        valueList += serializeLiteral(v) + " ";
×
228
                    }
229
                }
×
230
                expanded = expanded.replaceAll("values\\s*\\?" + ph + "\\s*\\{\\s*\\}", "values ?" + ph + " { " + valueList + "}");
×
231
            } else {
×
232
                String val = parameters.get(getParamName(ph));
×
233
                log.info("val: ", val);
×
234
                if (!isOptionalPlaceholder(ph) && val == null) {
×
235
                    // TODO throw exception
236
                    return null;
×
237
                }
238
                if (val == null) continue;
×
239
                if (isIriPlaceholder(ph)) {
×
240
                    expanded = expanded.replaceAll("\\?" + ph, serializeIri(val));
×
241
                } else {
242
                    expanded = expanded.replaceAll("\\?" + ph, serializeLiteral(val));
×
243
                }
244
            }
245
        }
×
246
        log.info("expanded: ", expanded);
×
247
        return expanded;
×
248
    }
249

250
    public static String escapeLiteral(String s) {
251
        return s.replace("\\", "\\\\").replace("\n", "\\n").replace("\"", "\\\"");
11✔
252
    }
253

254
    public static boolean isOptionalPlaceholder(String placeholder) {
255
        return placeholder.startsWith("__");
×
256
    }
257

258
    public static boolean isMultiPlaceholder(String placeholder) {
259
        return placeholder.endsWith("_multi") || placeholder.endsWith("_multi_iri");
×
260
    }
261

262
    public static boolean isIriPlaceholder(String placeholder) {
263
        return placeholder.endsWith("_iri");
×
264
    }
265

266
    public static String getParamName(String placeholder) {
267
        return placeholder.replaceFirst("^_+", "").replaceFirst("_iri$", "").replaceFirst("_multi$", "");
×
268
    }
269

270
    public static String serializeIri(String iriString) {
271
        return "<" + iriString + ">";
×
272
    }
273

274
    public static String serializeLiteral(String literalString) {
275
        return "\"" + escapeLiteral(literalString) + "\"";
×
276
    }
277

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