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

knowledgepixels / nanopub-query / 26774597310

01 Jun 2026 06:41PM UTC coverage: 58.598% (-1.0%) from 59.597%
26774597310

push

github

web-flow
Merge pull request #118 from knowledgepixels/fix/issue-117-getenvstring-subprocess

fix(env): read environment via System.getenv instead of forking a subprocess (#117)

471 of 896 branches covered (52.57%)

Branch coverage included in aggregate %.

1359 of 2227 relevant lines covered (61.02%)

9.34 hits per line

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

95.7
src/main/java/com/knowledgepixels/query/Utils.java
1
package com.knowledgepixels.query;
2

3
import java.io.IOException;
4
import java.io.InputStream;
5
import java.nio.charset.StandardCharsets;
6
import java.util.ArrayList;
7
import java.util.HashMap;
8
import java.util.List;
9
import java.util.Map;
10
import java.util.Properties;
11

12
import org.apache.commons.lang3.StringUtils;
13
import org.apache.http.client.config.CookieSpecs;
14
import org.apache.http.client.config.RequestConfig;
15
import org.eclipse.rdf4j.model.IRI;
16
import org.eclipse.rdf4j.model.Value;
17
import org.eclipse.rdf4j.model.ValueFactory;
18
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
19
import org.eclipse.rdf4j.query.BindingSet;
20
import org.eclipse.rdf4j.query.QueryLanguage;
21
import org.eclipse.rdf4j.query.TupleQuery;
22
import org.eclipse.rdf4j.query.TupleQueryResult;
23
import org.eclipse.rdf4j.repository.RepositoryConnection;
24
import org.nanopub.vocabulary.NPA;
25
import org.slf4j.Logger;
26
import org.slf4j.LoggerFactory;
27

28
import com.google.common.hash.Hashing;
29

30
/**
31
 * Utils class for Nanopub Registry.
32
 */
33
public class Utils {
34

35
    private Utils() {
36
    }  // no instances allowed
37

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

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

42
    private static Map<String, Value> hashToObjMap;
43

44
    /**
45
     * Returns reverse hashing map.
46
     *
47
     * @return Map from hashes to their original objects
48
     */
49
    static Map<String, Value> getHashToObjectMap() {
50
        if (hashToObjMap == null) {
6✔
51
            hashToObjMap = new HashMap<>();
12✔
52
            try (RepositoryConnection conn = TripleStore.get().getAdminRepoConnection()) {
9✔
53
                TupleQuery query = conn.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT * { graph ?g { ?s ?p ?o } }");
15✔
54
                query.setBinding("g", NPA.GRAPH);
12✔
55
                query.setBinding("p", NPA.IS_HASH_OF);
12✔
56
                try (TupleQueryResult r = query.evaluate()) {
9✔
57
                    while (r.hasNext()) {
9✔
58
                        BindingSet b = r.next();
12✔
59
                        String hash = b.getBinding("s").getValue().stringValue();
18✔
60
                        hash = StringUtils.replace(hash, NPA.HASH.toString(), "");
18✔
61
                        hashToObjMap.put(hash, b.getBinding("o").getValue());
24✔
62
                    }
3✔
63
                }
64
            }
65
        }
66
        return hashToObjMap;
6✔
67
    }
68

69
    /**
70
     * Returns the original object for the given hash value.
71
     *
72
     * @param hash The hash value
73
     * @return The original object
74
     */
75
    public static Value getObjectForHash(String hash) {
76
        return getHashToObjectMap().get(hash);
15✔
77
    }
78

79
    /**
80
     * Creates a hash value for the object and remembers it.
81
     *
82
     * @param obj Object to be hashed
83
     * @return hash value
84
     */
85
    public static String createHash(Object obj) {
86
        String hash = Hashing.sha256().hashString(obj.toString(), StandardCharsets.UTF_8).toString();
21✔
87

88
        if (!getHashToObjectMap().containsKey(hash)) {
12✔
89
            Value objV = getValue(obj);
9✔
90
            try (RepositoryConnection conn = TripleStore.get().getAdminRepoConnection()) {
9✔
91
                conn.add(vf.createStatement(vf.createIRI(NPA.HASH + hash), NPA.IS_HASH_OF, objV, NPA.GRAPH));
45✔
92
            }
93
            getHashToObjectMap().put(hash, objV);
15✔
94
        }
95
        return hash;
6✔
96
    }
97

98
    /**
99
     * Returns the object as a Value object.
100
     *
101
     * @param obj Input object
102
     * @return A Value object with the string content of the input object
103
     */
104
    static Value getValue(Object obj) {
105
        if (obj instanceof Value) {
9✔
106
            return (Value) obj;
9✔
107
        } else {
108
            return vf.createLiteral(obj.toString());
15✔
109
        }
110
    }
111

112
    /**
113
     * Returns a short "name" for the given public key
114
     *
115
     * @param pubkey Public key string
116
     * @return Short "name"
117
     */
118
    public static String getShortPubkeyName(String pubkey) {
119
        return pubkey.replaceFirst("^(.).{39}(.{5}).*$", "$1..$2..");
15✔
120
    }
121

122
    /**
123
     * Executes a query on the given connection to return the first objects matching the input.
124
     *
125
     * @param conn  The repository connection
126
     * @param graph Graph (=context) IRI
127
     * @param subj  Subject IRI
128
     * @param pred  Predicate IRI
129
     * @return the first object to match the pattern
130
     */
131
    public static Value getObjectForPattern(RepositoryConnection conn, IRI graph, IRI subj, IRI pred) {
132
        TupleQueryResult r = conn.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT * { graph <" + graph.stringValue() + "> { <" + subj.stringValue() + "> <" + pred.stringValue() + "> ?o } }").evaluate();
36✔
133
        try (r) {
6✔
134
            if (!r.hasNext()) return null;
21✔
135
            return r.next().getBinding("o").getValue();
27✔
136
        }
12!
137
    }
138

139
    /**
140
     * Executes a query on the given connection to return all objects matching the input.
141
     *
142
     * @param conn  The repository connection
143
     * @param graph Graph (=context) IRI
144
     * @param subj  Subject IRI
145
     * @param pred  Predicate IRI
146
     * @return a list of all objects matching the pattern
147
     */
148
    public static List<Value> getObjectsForPattern(RepositoryConnection conn, IRI graph, IRI subj, IRI pred) {
149
        List<Value> values = new ArrayList<>();
12✔
150
        TupleQueryResult r = conn.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT * { graph <" + graph.stringValue() + "> { <" + subj.stringValue() + "> <" + pred.stringValue() + "> ?o } }").evaluate();
36✔
151
        try (r) {
6✔
152
            while (r.hasNext()) {
9✔
153
                values.add(r.next().getBinding("o").getValue());
30✔
154
            }
155
            return values;
12✔
156
        }
157
    }
158

159
    /**
160
     * Returns the system environment variable content for the given environment variable name.
161
     *
162
     * @param envVarName   environment variable name
163
     * @param defaultValue default value if not found
164
     * @return environment variable value
165
     */
166
    public static String getEnvString(String envVarName, String defaultValue) {
167
        String s = getRawEnv(envVarName);
9✔
168
        if (s != null && !s.isEmpty()) {
15✔
169
            return s;
6✔
170
        }
171
        return defaultValue;
6✔
172
    }
173

174
    /**
175
     * Reads a single environment variable from the JVM's in-memory environment
176
     * block. The previous implementation used Apache Commons Exec's
177
     * {@code EnvironmentUtils.getProcEnvironment()}, which forks a subprocess
178
     * ({@code env}/{@code sh}) and parses its stdout on every call. That is neither
179
     * thread-safe nor robust under load, and since {@link #getEnvString} is on the
180
     * per-nanopub hot path ({@link FeatureFlags#fullRepoEnabled()} and friends,
181
     * called from the 4-thread loading pool) an intermittently mangled read made
182
     * {@code fullRepoEnabled()} evaluate to {@code false} and silently skip the
183
     * full-repo write for individual nanopubs — leaving {@code full} behind
184
     * {@code meta} undetectably (issue #117).
185
     *
186
     * <p>Extracted as a package-private seam because {@link System} static methods
187
     * cannot be mocked directly with Mockito; tests stub this instead.
188
     *
189
     * @param envVarName environment variable name
190
     * @return the raw value, or {@code null} if unset
191
     */
192
    static String getRawEnv(String envVarName) {
193
        return System.getenv(envVarName);
9✔
194
    }
195

196
    /**
197
     * Returns the system environment variable content as an integer for the given environment variable name.
198
     *
199
     * @param envVarName   environment variable name
200
     * @param defaultValue default value if not found
201
     * @return environment variable value interpreted as an integer
202
     */
203
    public static int getEnvInt(String envVarName, int defaultValue) {
204
        try {
205
            String s = getEnvString(envVarName, null);
12✔
206
            if (s != null) {
6✔
207
                return Integer.parseInt(s);
9✔
208
            }
209
        } catch (Exception ex) {
3✔
210
            log.info("Could not get environment variable", ex);
12✔
211
        }
3✔
212
        return defaultValue;
6✔
213
    }
214

215
    private static String version;
216

217
    /**
218
     * Returns the application version, read from the filtered version.properties resource.
219
     *
220
     * @return the project version, or "unknown" if it cannot be read
221
     */
222
    public static String getVersion() {
223
        String v = version;
6✔
224
        if (v != null) {
6✔
225
            return v;
6✔
226
        }
227
        Properties p = new Properties();
12✔
228
        try (InputStream in = Utils.class.getResourceAsStream("/version.properties")) {
12✔
229
            if (in != null) {
6!
230
                p.load(in);
9✔
231
            }
232
        } catch (IOException ex) {
×
233
            log.warn("Could not read version.properties", ex);
×
234
        }
3✔
235
        v = p.getProperty("version", "unknown");
15✔
236
        version = v;
6✔
237
        return v;
6✔
238
    }
239

240
    /**
241
     * Default query to be shown in YASGUI client.
242
     */
243
    public static final String defaultQuery = """
244
            prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
245
            prefix dct: <http://purl.org/dc/terms/>
246
            prefix np: <http://www.nanopub.org/nschema#>
247
            prefix npa: <http://purl.org/nanopub/admin/>
248
            prefix npx: <http://purl.org/nanopub/x/>
249
            
250
            select * where {
251
            ## Info about this repo:
252
              npa:thisRepo ?pred ?obj .
253
            ## Search for nanopublications:
254
            # graph npa:graph {
255
            #   ?np npa:hasValidSignatureForPublicKey ?pubkey .
256
            #   filter not exists { ?npx npx:invalidates ?np ; npa:hasValidSignatureForPublicKey ?pubkey . }
257
            #   ?np dct:created ?date .
258
            #   ?np np:hasAssertion ?a .
259
            #   optional { ?np rdfs:label ?label }
260
            # }
261
            } limit 10""";
262

263
    /**
264
     * Get the HTTP request config for fetching nanopublications.
265
     *
266
     * @return the HTTP client
267
     */
268
    static RequestConfig getHttpRequestConfig() {
269
        return RequestConfig.custom()
12✔
270
                .setConnectTimeout(getEnvInt("NANOPUB_QUERY_FETCHING_CONNECT_TIMEOUT", 10000))
12✔
271
                .setConnectionRequestTimeout(getEnvInt("NANOPUB_QUERY_FETCHING_CONNECTION_REQUEST_TIMEOUT", 1000))
12✔
272
                .setSocketTimeout(getEnvInt("NANOPUB_QUERY_FETCHING_SOCKET_TIMEOUT", 10000))
9✔
273
                .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
6✔
274
    }
275

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