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

knowledgepixels / nanodash / 26447726762

26 May 2026 12:20PM UTC coverage: 20.38% (-0.4%) from 20.748%
26447726762

Pull #468

github

web-flow
Merge f3d799e10 into 65b0c8452
Pull Request #468: Source space data from nanopub-query spaces repo

1005 of 6256 branches covered (16.06%)

Branch coverage included in aggregate %.

2600 of 11433 relevant lines covered (22.74%)

3.25 hits per line

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

38.14
src/main/java/com/knowledgepixels/nanodash/GrlcQuery.java
1
package com.knowledgepixels.nanodash;
2

3
import com.knowledgepixels.nanodash.component.QueryParamField;
4
import org.nanopub.extra.services.QueryRef;
5
import org.nanopub.extra.services.QueryTemplate;
6
import org.slf4j.Logger;
7
import org.slf4j.LoggerFactory;
8

9
import com.google.common.cache.Cache;
10
import com.google.common.cache.CacheBuilder;
11

12
import java.util.ArrayList;
13
import java.util.HashMap;
14
import java.util.List;
15
import java.util.Map;
16
import java.util.concurrent.TimeUnit;
17
import java.util.regex.Matcher;
18
import java.util.regex.Pattern;
19

20
/**
21
 * Represents a GRLC query extracted from a nanopublication.
22
 * <p>
23
 * Query parsing (SPARQL, endpoint, label, description, placeholders) is inherited from
24
 * {@link QueryTemplate} in nanopub-java. This subclass adds Nanodash-specific concerns:
25
 * an instance cache with {@link #get} factory methods, and integration with the
26
 * {@link QueryParamField} form components used by the query UI.
27
 */
28
public class GrlcQuery extends QueryTemplate {
29

30
    private static final Logger logger = LoggerFactory.getLogger(GrlcQuery.class);
9✔
31

32
    private static final Cache<String, GrlcQuery> instanceMap = CacheBuilder.newBuilder()
6✔
33
        .maximumSize(5_000)
9✔
34
        .expireAfterAccess(24, TimeUnit.HOURS)
3✔
35
        .build();
6✔
36

37
    private static final Pattern ARTIFACT_CODE_PATTERN = Pattern.compile("RA[A-Za-z0-9\\-_]{43}");
12✔
38

39
    /**
40
     * Returns a singleton instance of GrlcQuery for the given QueryRef.
41
     *
42
     * @param ref the QueryRef object containing the query name
43
     * @return a GrlcQuery instance
44
     */
45
    public static GrlcQuery get(QueryRef ref) {
46
        return get(ref.getQueryId());
12✔
47
    }
48

49
    /**
50
     * Returns a singleton instance of GrlcQuery for the given query ID.
51
     *
52
     * @param id the unique identifier or URI of the query
53
     * @return a GrlcQuery instance
54
     */
55
    public static GrlcQuery get(String id) {
56
        if (id == null) return null;
12✔
57
        GrlcQuery cached = instanceMap.getIfPresent(id);
15✔
58
        if (cached == null) {
6!
59
            try {
60
                GrlcQuery q = new GrlcQuery(id);
15✔
61
                id = q.getQueryId();
9✔
62
                cached = instanceMap.getIfPresent(id);
15✔
63
                if (cached != null) return cached;
12✔
64
                instanceMap.put(id, q);
12✔
65
                cached = q;
6✔
66
            } catch (Exception ex) {
3✔
67
                logger.error("Could not load query: {}", id, ex);
15✔
68
            }
3✔
69
        }
70
        return cached;
6✔
71
    }
72

73
    /**
74
     * Constructs a GrlcQuery by parsing the given query ID or URI, fetching the underlying
75
     * nanopublication through Nanodash's {@link Utils#getNanopub(String)} (which uses the
76
     * configured registries and a local cache).
77
     *
78
     * @param id the query ID or URI
79
     * @throws IllegalArgumentException if the ID is invalid or the nanopublication does not
80
     *                                  contain exactly one query
81
     */
82
    private GrlcQuery(String id) {
83
        super(Utils.getNanopub(extractArtifactCode(id)), id);
18✔
84
    }
3✔
85

86
    private static String extractArtifactCode(String id) {
87
        if (id == null) {
6!
88
            throw new IllegalArgumentException("Null value for query ID");
×
89
        }
90
        Matcher m = ARTIFACT_CODE_PATTERN.matcher(id);
12✔
91
        if (m.find()) {
9!
92
            return m.group();
9✔
93
        }
94
        throw new IllegalArgumentException("Not a valid query ID or URI: " + id);
×
95
    }
96

97
    /**
98
     * Creates a list of query parameter fields for the placeholders in the query.
99
     *
100
     * @param markupId The markup ID for the fields.
101
     * @return A list of query parameter fields.
102
     */
103
    public List<QueryParamField> createParamFields(String markupId) {
104
        List<QueryParamField> l = new ArrayList<>();
12✔
105
        for (String s : getPlaceholdersList()) {
21!
106
            l.add(new QueryParamField(markupId, s));
×
107
        }
×
108
        return l;
6✔
109
    }
110

111
    /**
112
     * Expands the SPARQL query by substituting placeholder values from the given param fields.
113
     * Unlike the strict server-side expansion in {@link QueryTemplate}, missing/unset params are
114
     * simply skipped (not thrown as errors), to support the partial substitution used for the
115
     * Yasgui link. Placeholder conventions follow {@link QueryParamField} (which recognizes
116
     * {@code _multi_val} in addition to the standard suffixes).
117
     *
118
     * @param paramFields the list of query parameter fields with user-entered values
119
     * @return the expanded SPARQL query string
120
     */
121
    public String expandQuery(List<QueryParamField> paramFields) {
122
        Map<String, QueryParamField> fieldMap = new HashMap<>();
×
123
        for (QueryParamField f : paramFields) {
×
124
            fieldMap.put(f.getParamName(), f);
×
125
        }
×
126
        String expandedQueryContent = getSparql();
×
127
        for (String ph : getPlaceholdersList()) {
×
128
            String paramName = QueryParamField.getParamName(ph);
×
129
            QueryParamField field = fieldMap.get(paramName);
×
130
            if (field == null || !field.isSet()) continue;
×
131
            if (QueryParamField.isMultiPlaceholder(ph)) {
×
132
                String[] values = field.getValues();
×
133
                StringBuilder valueList = new StringBuilder();
×
134
                for (String v : values) {
×
135
                    if (isIriPlaceholder(ph)) {
×
136
                        valueList.append(serializeIri(v)).append(" ");
×
137
                    } else {
138
                        valueList.append(serializeLiteral(v)).append(" ");
×
139
                    }
140
                }
141
                expandedQueryContent = expandedQueryContent.replaceAll(
×
142
                    "values\\s*\\?" + ph + "\\s*\\{\\s*\\}",
143
                    "values ?" + ph + " { " + escapeSlashes(valueList.toString()) + "}"
×
144
                );
145
            } else {
×
146
                String val = field.getValues()[0];
×
147
                if (isIriPlaceholder(ph)) {
×
148
                    expandedQueryContent = expandedQueryContent.replaceAll("\\?" + ph + "(?![A-Za-z0-9_])", escapeSlashes(serializeIri(val)));
×
149
                } else {
150
                    expandedQueryContent = expandedQueryContent.replaceAll("\\?" + ph + "(?![A-Za-z0-9_])", escapeSlashes(serializeLiteral(val)));
×
151
                }
152
            }
153
        }
×
154
        return expandedQueryContent;
×
155
    }
156

157
    /**
158
     * Returns true if all mandatory (non-optional) param fields have values set.
159
     *
160
     * @param paramFields the list of query parameter fields
161
     * @return true if all mandatory fields are set
162
     */
163
    public static boolean allMandatoryFieldsSet(List<QueryParamField> paramFields) {
164
        for (QueryParamField f : paramFields) {
×
165
            if (!f.isOptional() && !f.isSet()) return false;
×
166
        }
×
167
        return true;
×
168
    }
169

170
    private static String escapeSlashes(String string) {
171
        return string.replace("\\", "\\\\");
×
172
    }
173

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