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

knowledgepixels / nanopub-registry / 24727603152

21 Apr 2026 02:18PM UTC coverage: 31.808% (+0.3%) from 31.558%
24727603152

push

github

ashleycaselli
chore(logging): enhance info error handling in Nanopub processing

280 of 986 branches covered (28.4%)

Branch coverage included in aggregate %.

839 of 2532 relevant lines covered (33.14%)

5.5 hits per line

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

92.75
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) {
6✔
59
            return v;
6✔
60
        }
61
        Properties p = new Properties();
12✔
62
        try (InputStream in = Utils.class.getResourceAsStream("/version.properties")) {
12✔
63
            if (in != null) {
6!
64
                p.load(in);
9✔
65
            }
66
        } catch (IOException ex) {
×
67
            logger.warn("Could not read version.properties", ex);
×
68
        }
3✔
69
        v = p.getProperty("version", "unknown");
15✔
70
        version = v;
6✔
71
        return v;
6✔
72
    }
73

74
    public static String getMimeType(RoutingContext context, String supported) {
75
        List<String> supportedList = Arrays.asList(StringUtils.split(supported, ','));
15✔
76
        String mimeType = supportedList.getFirst();
12✔
77
        String acceptHeader = context.request().getHeader("Accept");
15✔
78
        if (acceptHeader == null) {
6✔
79
            return mimeType;
6✔
80
        }
81
        try {
82
            mimeType = MIMEParse.bestMatch(supportedList, acceptHeader);
12✔
83
        } catch (Exception ex) {
×
84
            logger.error("Failed to parse Accept header '{}': {}", acceptHeader, ex.getMessage(), ex);
×
85
        }
3✔
86
        return mimeType;
6✔
87
    }
88

89
    public static String urlEncode(Object o) {
90
        return URLEncoder.encode((o == null ? "" : o.toString()), StandardCharsets.UTF_8);
27✔
91
    }
92

93
    public static String getHash(String s) {
94
        return Hashing.sha256().hashString(s, Charsets.UTF_8).toString();
18✔
95
    }
96

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

120
    private static ReadsEnvironment ENV_READER = new ReadsEnvironment(System::getenv);
15✔
121

122
    /**
123
     * Set the environment reader (used for testing purposes).
124
     *
125
     * @param reader the environment reader to set
126
     */
127
    public static void setEnvReader(ReadsEnvironment reader) {
128
        ENV_READER = reader;
6✔
129
    }
3✔
130

131
    /**
132
     * Get an environment variable, returning a default value if not set.
133
     *
134
     * @param name         the name of the environment variable
135
     * @param defaultValue the default value to return if the variable is not set
136
     * @return the value of the environment variable, or the default value if not set
137
     */
138
    public static String getEnv(String name, String defaultValue) {
139
        logger.debug("Reading environment variable '{}'", name);
12✔
140
        String value = ENV_READER.getEnv(name);
12✔
141
        if (value == null) {
6✔
142
            value = defaultValue;
6✔
143
            logger.debug("Environment variable '{}' not set; using default: '{}'", name, defaultValue);
15✔
144
        }
145
        return value;
6✔
146
    }
147

148
    /**
149
     * Get the type hash for a given type, recording it in the database if necessary.
150
     *
151
     * @param mongoSession the MongoDB client session
152
     * @param type         the type to get the hash for
153
     * @return the type hash
154
     */
155
    public static String getTypeHash(ClientSession mongoSession, Object type) {
156
        String typeHash = Utils.getHash(type.toString());
12✔
157
        if (type.toString().equals("$")) {
15✔
158
            typeHash = "$";
9✔
159
        } else {
160
            RegistryDB.recordHash(mongoSession, type.toString());
12✔
161
        }
162
        return typeHash;
6✔
163
    }
164

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

182
    /**
183
     * Check if the given status indicates an unloaded entry.
184
     *
185
     * @param status the status to check
186
     * @return true if the status indicates an unloaded entry, false otherwise
187
     */
188
    public static boolean isUnloadedStatus(String status) {
189
        if (status.equals(EntryStatus.seen.getValue())) {
15✔
190
            return true;  // only exists in "accounts_loading"?
6✔
191
        }
192
        return status.equals(EntryStatus.skipped.getValue());
15✔
193
    }
194

195
    /**
196
     * Check if the given status indicates a core loaded entry.
197
     *
198
     * @param status the status to check
199
     * @return true if the status indicates a core loaded entry, false otherwise
200
     */
201
    public static boolean isCoreLoadedStatus(String status) {
202
        if (status.equals(EntryStatus.visited.getValue())) {
15✔
203
            return true;  // only exists in "accounts_loading"?
6✔
204
        }
205
        if (status.equals(EntryStatus.expanded.getValue())) {
15✔
206
            return true;  // only exists in "accounts_loading"?
6✔
207
        }
208
        if (status.equals(EntryStatus.processed.getValue())) {
15✔
209
            return true;  // only exists in "accounts_loading"?
6✔
210
        }
211
        if (status.equals(EntryStatus.aggregated.getValue())) {
15✔
212
            return true;  // only exists in "accounts_loading"?
6✔
213
        }
214
        if (status.equals(EntryStatus.approved.getValue())) {
15✔
215
            return true;  // only exists in "accounts_loading"?
6✔
216
        }
217
        if (status.equals(EntryStatus.contested.getValue())) {
15✔
218
            return true;
6✔
219
        }
220
        if (status.equals(EntryStatus.toLoad.getValue())) {
15✔
221
            return true;  // only exists in "accounts_loading"?
6✔
222
        }
223
        return status.equals(EntryStatus.loaded.getValue());
15✔
224
    }
225

226
    /**
227
     * Check if the given status indicates a fully loaded entry.
228
     *
229
     * @param status the status to check
230
     * @return true if the status indicates a fully loaded entry, false otherwise
231
     */
232
    public static boolean isFullyLoadedStatus(String status) {
233
        return status.equals(EntryStatus.loaded.getValue());
15✔
234
    }
235

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

238
    public static final String TYPE_JSON = "application/json";
239
    public static final String TYPE_TRIG = "application/trig";
240
    public static final String TYPE_JELLY = "application/x-jelly-rdf";
241
    public static final String TYPE_JSONLD = "application/ld+json";
242
    public static final String TYPE_NQUADS = "application/n-quads";
243
    public static final String TYPE_TRIX = "application/trix";
244
    public static final String TYPE_HTML = "text/html";
245

246
    // Content types supported on a ListPage
247
    public static final String SUPPORTED_TYPES_LIST = TYPE_JSON + "," + TYPE_JELLY + "," + TYPE_HTML;
248
    // Content types supported on a NanopubPage
249
    public static final String SUPPORTED_TYPES_NANOPUB = TYPE_TRIG + "," + TYPE_JELLY + "," + TYPE_JSONLD + "," + TYPE_NQUADS + "," + TYPE_TRIX + "," + TYPE_HTML;
250

251
    private static Map<String, String> extensionTypeMap;
252

253
    /**
254
     * Get the type corresponding to a given file extension.
255
     *
256
     * @param extension the file extension
257
     * @return the corresponding MIME type, or null if not found
258
     */
259
    public static String getType(String extension) {
260
        if (extension == null) {
6✔
261
            return null;
6✔
262
        }
263
        if (extensionTypeMap == null) {
6✔
264
            extensionTypeMap = new HashMap<>();
12✔
265
            extensionTypeMap.put("trig", TYPE_TRIG);
15✔
266
            extensionTypeMap.put("jelly", TYPE_JELLY);
15✔
267
            extensionTypeMap.put("jsonld", TYPE_JSONLD);
15✔
268
            extensionTypeMap.put("nq", TYPE_NQUADS);
15✔
269
            extensionTypeMap.put("xml", TYPE_TRIX);
15✔
270
            extensionTypeMap.put("html", TYPE_HTML);
15✔
271
            extensionTypeMap.put("json", TYPE_JSON);
15✔
272
        }
273
        return extensionTypeMap.get(extension);
15✔
274
    }
275

276
    private static List<String> peerUrls;
277

278
    /**
279
     * Get the list of peer registry URLs.
280
     *
281
     * @return the list of peer registry URLs
282
     */
283
    public static List<String> getPeerUrls() {
284
        if (peerUrls == null) {
6✔
285
            peerUrls = new ArrayList<>();
12✔
286
            String envPeerUrls = getEnv("REGISTRY_PEER_URLS", "");
12✔
287
            String thisRegistryUrl = getEnv("REGISTRY_SERVICE_URL", "");
12✔
288
            if (!envPeerUrls.isEmpty()) {
9✔
289
                for (String peerUrl : envPeerUrls.trim().split("\\s+")) {
60✔
290
                    if (thisRegistryUrl.equals(peerUrl)) {
12!
291
                        continue;
×
292
                    }
293
                    peerUrls.add(peerUrl);
12✔
294
                }
295
            } else {
296
                NanopubSetting setting;
297
                try {
298
                    setting = getSetting();
6✔
299
                } catch (MalformedNanopubException | IOException ex) {
3✔
300
                    logger.error("Failed to load registry setting from file", ex);
12✔
301
                    throw new RuntimeException(ex);
15✔
302
                }
3✔
303
                for (IRI iri : setting.getBootstrapServices()) {
33✔
304
                    String peerUrl = iri.stringValue();
9✔
305
                    if (thisRegistryUrl.equals(peerUrl)) {
12!
306
                        continue;
×
307
                    }
308
                    peerUrls.add(peerUrl);
12✔
309
                }
3✔
310
            }
311
        }
312
        return peerUrls;
6✔
313
    }
314

315
    private static volatile NanopubSetting settingNp;
316

317
    /**
318
     * Get the nanopublication setting.
319
     *
320
     * @return the nanopublication setting
321
     * @throws RDF4JException            if there is an error retrieving the setting
322
     * @throws MalformedNanopubException if the setting nanopublication is malformed
323
     * @throws IOException               if there is an I/O error
324
     */
325
    public static NanopubSetting getSetting() throws RDF4JException, MalformedNanopubException, IOException {
326
        if (settingNp == null) {
6✔
327
            synchronized (Utils.class) {
12✔
328
                if (settingNp == null) {
6!
329
                    String settingPath = getEnv("REGISTRY_SETTING_FILE", "/data/setting.trig");
12✔
330
                    settingNp = new NanopubSetting(new NanopubImpl(new File(settingPath)));
33✔
331
                }
332
            }
9✔
333
        }
334
        return settingNp;
6✔
335
    }
336

337
    /**
338
     * Get a random peer registry URL.
339
     *
340
     * @return a random peer registry URL
341
     * @throws RDF4JException if there is an error retrieving the peer URLs
342
     */
343
    public static String getRandomPeer() throws RDF4JException {
344
        List<String> peerUrls = getPeerUrls();
6✔
345
        return peerUrls.get(random.nextInt(peerUrls.size()));
24✔
346
    }
347

348
    private static final Random random = new Random();
12✔
349

350
    /**
351
     * Get the random number generator.
352
     *
353
     * @return the random number generator
354
     */
355
    public static Random getRandom() {
356
        return random;
6✔
357
    }
358

359
    private static final Gson g = new Gson();
12✔
360
    private static Type listType = new TypeToken<List<String>>() {
21✔
361
    }.getType();
6✔
362

363
    /**
364
     * Retrieve a list of strings from a JSON URL.
365
     *
366
     * @param url the URL to retrieve the JSON from
367
     * @return the list of strings
368
     * @throws JsonIOException     if there is an error reading the JSON
369
     * @throws JsonSyntaxException if the JSON syntax is invalid
370
     * @throws IOException         if there is an I/O error
371
     * @throws URISyntaxException  if the URL syntax is invalid
372
     */
373
    public static List<String> retrieveListFromJsonUrl(String url) throws JsonIOException, JsonSyntaxException, IOException, URISyntaxException {
374
        return g.fromJson(new InputStreamReader(new URI(url).toURL().openStream()), listType);
×
375
    }
376

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