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

knowledgepixels / nanopub-registry / 24499269750

16 Apr 2026 08:06AM UTC coverage: 31.816% (+0.2%) from 31.57%
24499269750

push

github

web-flow
Merge pull request #109 from knowledgepixels/feature/expose-version

feat: expose registry version in JSON and response headers

280 of 988 branches covered (28.34%)

Branch coverage included in aggregate %.

838 of 2526 relevant lines covered (33.17%)

5.48 hits per line

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

92.27
src/main/java/com/knowledgepixels/registry/Utils.java
1
package com.knowledgepixels.registry;
2

3
import com.github.jsonldjava.shaded.com.google.common.base.Charsets;
4
import com.github.jsonldjava.shaded.com.google.common.reflect.TypeToken;
5
import com.google.common.hash.Hashing;
6
import com.google.gson.Gson;
7
import com.google.gson.JsonIOException;
8
import com.google.gson.JsonSyntaxException;
9
import com.mongodb.client.ClientSession;
10
import io.vertx.ext.web.RoutingContext;
11
import net.trustyuri.TrustyUriUtils;
12
import org.apache.commons.lang.StringUtils;
13
import org.commonjava.mimeparse.MIMEParse;
14
import org.eclipse.rdf4j.common.exception.RDF4JException;
15
import org.eclipse.rdf4j.model.IRI;
16
import org.eclipse.rdf4j.model.Resource;
17
import org.eclipse.rdf4j.model.Statement;
18
import org.eclipse.rdf4j.model.util.Values;
19
import org.nanopub.MalformedNanopubException;
20
import org.nanopub.Nanopub;
21
import org.nanopub.NanopubImpl;
22
import org.nanopub.NanopubUtils;
23
import org.nanopub.extra.setting.NanopubSetting;
24
import org.nanopub.vocabulary.NPX;
25
import org.slf4j.Logger;
26
import org.slf4j.LoggerFactory;
27

28
import java.io.File;
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.io.InputStreamReader;
32
import java.lang.reflect.Type;
33
import java.net.URI;
34
import java.net.URISyntaxException;
35
import java.net.URLEncoder;
36
import java.nio.charset.StandardCharsets;
37
import java.util.*;
38

39
/**
40
 * Utility class for the Nanopub Registry.
41
 */
42
public class Utils {
43

44
    private static final Logger logger = LoggerFactory.getLogger(Utils.class);
9✔
45

46
    private Utils() {
47
    }  // no instances allowed
48

49
    private static volatile String version;
50

51
    /**
52
     * Returns the registry's version (from Maven at build time, via a filtered
53
     * {@code version.properties} resource). Returns {@code "unknown"} if the
54
     * resource is unavailable.
55
     */
56
    public static String getVersion() {
57
        String v = version;
6✔
58
        if (v != null) return v;
12✔
59
        Properties p = new Properties();
12✔
60
        try (InputStream in = Utils.class.getResourceAsStream("/version.properties")) {
12✔
61
            if (in != null) p.load(in);
15!
62
        } catch (IOException ex) {
×
63
            logger.warn("Could not read version.properties", ex);
×
64
        }
3✔
65
        v = p.getProperty("version", "unknown");
15✔
66
        version = v;
6✔
67
        return v;
6✔
68
    }
69

70
    public static String getMimeType(RoutingContext context, String supported) {
71
        List<String> supportedList = Arrays.asList(StringUtils.split(supported, ','));
15✔
72
        String mimeType = supportedList.getFirst();
12✔
73
        String acceptHeader = context.request().getHeader("Accept");
15✔
74
        if (acceptHeader == null) return mimeType;
12✔
75
        try {
76
            mimeType = MIMEParse.bestMatch(supportedList, acceptHeader);
12✔
77
        } catch (Exception ex) {
×
78
            logger.error("Error parsing Accept header.", ex);
×
79
        }
3✔
80
        return mimeType;
6✔
81
    }
82

83
    public static String urlEncode(Object o) {
84
        return URLEncoder.encode((o == null ? "" : o.toString()), StandardCharsets.UTF_8);
27✔
85
    }
86

87
    public static String getHash(String s) {
88
        return Hashing.sha256().hashString(s, Charsets.UTF_8).toString();
18✔
89
    }
90

91
    /**
92
     * Get the IDs of nanopublications invalidated by the given nanopublication.
93
     *
94
     * @param np the nanopublication to check
95
     * @return a set of IRI IDs of invalidated nanopublications
96
     */
97
    public static Set<IRI> getInvalidatedNanopubIds(Nanopub np) {
98
        Set<IRI> invalidatedNanopubs = new HashSet<>();
12✔
99
        for (Statement st : NanopubUtils.getStatements(np)) {
33✔
100
            if (!(st.getObject() instanceof IRI)) continue;
15✔
101
            Resource subject = st.getSubject();
9✔
102
            IRI predicate = st.getPredicate();
9✔
103
            if ((predicate.equals(NPX.RETRACTS) || predicate.equals(NPX.INVALIDATES)) || (predicate.equals(NPX.SUPERSEDES) && subject.equals(np.getUri()))) {
51!
104
                if (TrustyUriUtils.isPotentialTrustyUri(st.getObject().stringValue())) {
15!
105
                    invalidatedNanopubs.add((IRI) st.getObject());
18✔
106
                }
107
            }
108
        }
3✔
109
        return invalidatedNanopubs;
6✔
110
    }
111

112
    private static ReadsEnvironment ENV_READER = new ReadsEnvironment(System::getenv);
15✔
113

114
    /**
115
     * Set the environment reader (used for testing purposes).
116
     *
117
     * @param reader the environment reader to set
118
     */
119
    public static void setEnvReader(ReadsEnvironment reader) {
120
        ENV_READER = reader;
6✔
121
    }
3✔
122

123
    /**
124
     * Get an environment variable, returning a default value if not set.
125
     *
126
     * @param name         the name of the environment variable
127
     * @param defaultValue the default value to return if the variable is not set
128
     * @return the value of the environment variable, or the default value if not set
129
     */
130
    public static String getEnv(String name, String defaultValue) {
131
        logger.info("Retrieving environment variable: {}", name);
12✔
132
        String value = ENV_READER.getEnv(name);
12✔
133
        if (value == null) {
6✔
134
            value = defaultValue;
6✔
135
            logger.info("The variable: {} is not set. Using default value: {}", name, defaultValue);
15✔
136
        }
137
        return value;
6✔
138
    }
139

140
    /**
141
     * Get the type hash for a given type, recording it in the database if necessary.
142
     *
143
     * @param mongoSession the MongoDB client session
144
     * @param type         the type to get the hash for
145
     * @return the type hash
146
     */
147
    public static String getTypeHash(ClientSession mongoSession, Object type) {
148
        String typeHash = Utils.getHash(type.toString());
12✔
149
        if (type.toString().equals("$")) {
15✔
150
            typeHash = "$";
9✔
151
        } else {
152
            RegistryDB.recordHash(mongoSession, type.toString());
12✔
153
        }
154
        return typeHash;
6✔
155
    }
156

157
    /**
158
     * Get a label for an agent ID, truncating if necessary.
159
     *
160
     * @param agentId the agent ID
161
     * @return the agent label
162
     */
163
    public static String getAgentLabel(String agentId) {
164
        if (agentId == null || agentId.isBlank()) {
15✔
165
            throw new IllegalArgumentException("Agent ID cannot be null or blank");
15✔
166
        }
167
        agentId = agentId.replaceFirst("^https://orcid\\.org/", "orcid:");
15✔
168
        if (agentId.length() > 55) {
12✔
169
            return agentId.substring(0, 50) + "...";
18✔
170
        }
171
        return agentId;
6✔
172
    }
173

174
    /**
175
     * Check if the given status indicates an unloaded entry.
176
     *
177
     * @param status the status to check
178
     * @return true if the status indicates an unloaded entry, false otherwise
179
     */
180
    public static boolean isUnloadedStatus(String status) {
181
        if (status.equals(EntryStatus.seen.getValue())) return true;  // only exists in "accounts_loading"?
21✔
182
        return status.equals(EntryStatus.skipped.getValue());
15✔
183
    }
184

185
    /**
186
     * Check if the given status indicates a core loaded entry.
187
     *
188
     * @param status the status to check
189
     * @return true if the status indicates a core loaded entry, false otherwise
190
     */
191
    public static boolean isCoreLoadedStatus(String status) {
192
        if (status.equals(EntryStatus.visited.getValue())) return true;  // only exists in "accounts_loading"?
21✔
193
        if (status.equals(EntryStatus.expanded.getValue())) return true;  // only exists in "accounts_loading"?
21✔
194
        if (status.equals(EntryStatus.processed.getValue())) return true;  // only exists in "accounts_loading"?
21✔
195
        if (status.equals(EntryStatus.aggregated.getValue())) return true;  // only exists in "accounts_loading"?
21✔
196
        if (status.equals(EntryStatus.approved.getValue())) return true;  // only exists in "accounts_loading"?
21✔
197
        if (status.equals(EntryStatus.contested.getValue())) return true;
21✔
198
        if (status.equals(EntryStatus.toLoad.getValue())) return true;  // only exists in "accounts_loading"?
21✔
199
        return status.equals(EntryStatus.loaded.getValue());
15✔
200
    }
201

202
    /**
203
     * Check if the given status indicates a fully loaded entry.
204
     *
205
     * @param status the status to check
206
     * @return true if the status indicates a fully loaded entry, false otherwise
207
     */
208
    public static boolean isFullyLoadedStatus(String status) {
209
        return status.equals(EntryStatus.loaded.getValue());
15✔
210
    }
211

212
    public static final IRI APPROVES_OF = Values.iri("http://purl.org/nanopub/x/approvesOf");
9✔
213

214
    public static final String TYPE_JSON = "application/json";
215
    public static final String TYPE_TRIG = "application/trig";
216
    public static final String TYPE_JELLY = "application/x-jelly-rdf";
217
    public static final String TYPE_JSONLD = "application/ld+json";
218
    public static final String TYPE_NQUADS = "application/n-quads";
219
    public static final String TYPE_TRIX = "application/trix";
220
    public static final String TYPE_HTML = "text/html";
221

222
    // Content types supported on a ListPage
223
    public static final String SUPPORTED_TYPES_LIST = TYPE_JSON + "," + TYPE_JELLY + "," + TYPE_HTML;
224
    // Content types supported on a NanopubPage
225
    public static final String SUPPORTED_TYPES_NANOPUB = TYPE_TRIG + "," + TYPE_JELLY + "," + TYPE_JSONLD + "," + TYPE_NQUADS + "," + TYPE_TRIX + "," + TYPE_HTML;
226

227
    private static Map<String, String> extensionTypeMap;
228

229
    /**
230
     * Get the type corresponding to a given file extension.
231
     *
232
     * @param extension the file extension
233
     * @return the corresponding MIME type, or null if not found
234
     */
235
    public static String getType(String extension) {
236
        if (extension == null) {
6✔
237
            return null;
6✔
238
        }
239
        if (extensionTypeMap == null) {
6✔
240
            extensionTypeMap = new HashMap<>();
12✔
241
            extensionTypeMap.put("trig", TYPE_TRIG);
15✔
242
            extensionTypeMap.put("jelly", TYPE_JELLY);
15✔
243
            extensionTypeMap.put("jsonld", TYPE_JSONLD);
15✔
244
            extensionTypeMap.put("nq", TYPE_NQUADS);
15✔
245
            extensionTypeMap.put("xml", TYPE_TRIX);
15✔
246
            extensionTypeMap.put("html", TYPE_HTML);
15✔
247
            extensionTypeMap.put("json", TYPE_JSON);
15✔
248
        }
249
        return extensionTypeMap.get(extension);
15✔
250
    }
251

252
    private static List<String> peerUrls;
253

254
    /**
255
     * Get the list of peer registry URLs.
256
     *
257
     * @return the list of peer registry URLs
258
     */
259
    public static List<String> getPeerUrls() {
260
        if (peerUrls == null) {
6✔
261
            peerUrls = new ArrayList<>();
12✔
262
            String envPeerUrls = getEnv("REGISTRY_PEER_URLS", "");
12✔
263
            String thisRegistryUrl = getEnv("REGISTRY_SERVICE_URL", "");
12✔
264
            if (!envPeerUrls.isEmpty()) {
9✔
265
                for (String peerUrl : envPeerUrls.trim().split("\\s+")) {
60✔
266
                    if (thisRegistryUrl.equals(peerUrl)) {
12!
267
                        continue;
×
268
                    }
269
                    peerUrls.add(peerUrl);
12✔
270
                }
271
            } else {
272
                NanopubSetting setting;
273
                try {
274
                    setting = getSetting();
6✔
275
                } catch (MalformedNanopubException | IOException ex) {
3✔
276
                    logger.error("Error loading registry setting: {}", ex.getMessage());
15✔
277
                    throw new RuntimeException(ex);
15✔
278
                }
3✔
279
                for (IRI iri : setting.getBootstrapServices()) {
33✔
280
                    String peerUrl = iri.stringValue();
9✔
281
                    if (thisRegistryUrl.equals(peerUrl)) {
12!
282
                        continue;
×
283
                    }
284
                    peerUrls.add(peerUrl);
12✔
285
                }
3✔
286
            }
287
        }
288
        return peerUrls;
6✔
289
    }
290

291
    private static volatile NanopubSetting settingNp;
292

293
    /**
294
     * Get the nanopublication setting.
295
     *
296
     * @return the nanopublication setting
297
     * @throws RDF4JException            if there is an error retrieving the setting
298
     * @throws MalformedNanopubException if the setting nanopublication is malformed
299
     * @throws IOException               if there is an I/O error
300
     */
301
    public static NanopubSetting getSetting() throws RDF4JException, MalformedNanopubException, IOException {
302
        if (settingNp == null) {
6✔
303
            synchronized (Utils.class) {
12✔
304
                if (settingNp == null) {
6!
305
                    String settingPath = getEnv("REGISTRY_SETTING_FILE", "/data/setting.trig");
12✔
306
                    settingNp = new NanopubSetting(new NanopubImpl(new File(settingPath)));
33✔
307
                }
308
            }
9✔
309
        }
310
        return settingNp;
6✔
311
    }
312

313
    /**
314
     * Get a random peer registry URL.
315
     *
316
     * @return a random peer registry URL
317
     * @throws RDF4JException if there is an error retrieving the peer URLs
318
     */
319
    public static String getRandomPeer() throws RDF4JException {
320
        List<String> peerUrls = getPeerUrls();
6✔
321
        return peerUrls.get(random.nextInt(peerUrls.size()));
24✔
322
    }
323

324
    private static final Random random = new Random();
12✔
325

326
    /**
327
     * Get the random number generator.
328
     *
329
     * @return the random number generator
330
     */
331
    public static Random getRandom() {
332
        return random;
6✔
333
    }
334

335
    private static final Gson g = new Gson();
12✔
336
    private static Type listType = new TypeToken<List<String>>() {
21✔
337
    }.getType();
6✔
338

339
    /**
340
     * Retrieve a list of strings from a JSON URL.
341
     *
342
     * @param url the URL to retrieve the JSON from
343
     * @return the list of strings
344
     * @throws JsonIOException     if there is an error reading the JSON
345
     * @throws JsonSyntaxException if the JSON syntax is invalid
346
     * @throws IOException         if there is an I/O error
347
     * @throws URISyntaxException  if the URL syntax is invalid
348
     */
349
    public static List<String> retrieveListFromJsonUrl(String url) throws JsonIOException, JsonSyntaxException, IOException, URISyntaxException {
350
        return g.fromJson(new InputStreamReader(new URI(url).toURL().openStream()), listType);
×
351
    }
352

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