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

knowledgepixels / nanopub-registry / 22870977169

09 Mar 2026 07:28PM UTC coverage: 27.117% (+1.6%) from 25.557%
22870977169

push

github

web-flow
Merge pull request #75 from knowledgepixels/74-peer-registry-sync

feat: add registry-to-registry peer sync for CHECK_NEW

166 of 676 branches covered (24.56%)

Branch coverage included in aggregate %.

545 of 1946 relevant lines covered (28.01%)

4.97 hits per line

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

87.95
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.InputStreamReader;
31
import java.lang.reflect.Type;
32
import java.net.URI;
33
import java.net.URISyntaxException;
34
import java.net.URLEncoder;
35
import java.nio.charset.StandardCharsets;
36
import java.util.*;
37

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

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

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

48
    public static String getMimeType(RoutingContext context, String supported) {
49
        List<String> supportedList = Arrays.asList(StringUtils.split(supported, ','));
×
50
        String mimeType = supportedList.getFirst();
×
51
        String acceptHeader = context.request().getHeader("Accept");
×
52
        if (acceptHeader == null) return mimeType;
×
53
        try {
54
            mimeType = MIMEParse.bestMatch(supportedList, acceptHeader);
×
55
        } catch (Exception ex) {
×
56
            logger.error("Error parsing Accept header.", ex);
×
57
        }
×
58
        return mimeType;
×
59
    }
60

61
    public static String urlEncode(Object o) {
62
        return URLEncoder.encode((o == null ? "" : o.toString()), StandardCharsets.UTF_8);
27✔
63
    }
64

65
    public static String getHash(String s) {
66
        return Hashing.sha256().hashString(s, Charsets.UTF_8).toString();
18✔
67
    }
68

69
    /**
70
     * Get the IDs of nanopublications invalidated by the given nanopublication.
71
     *
72
     * @param np the nanopublication to check
73
     * @return a set of IRI IDs of invalidated nanopublications
74
     */
75
    public static Set<IRI> getInvalidatedNanopubIds(Nanopub np) {
76
        Set<IRI> invalidatedNanopubs = new HashSet<>();
12✔
77
        for (Statement st : NanopubUtils.getStatements(np)) {
33✔
78
            if (!(st.getObject() instanceof IRI)) continue;
15✔
79
            Resource subject = st.getSubject();
9✔
80
            IRI predicate = st.getPredicate();
9✔
81
            if ((predicate.equals(NPX.RETRACTS) || predicate.equals(NPX.INVALIDATES)) || (predicate.equals(NPX.SUPERSEDES) && subject.equals(np.getUri()))) {
51!
82
                if (TrustyUriUtils.isPotentialTrustyUri(st.getObject().stringValue())) {
15!
83
                    invalidatedNanopubs.add((IRI) st.getObject());
18✔
84
                }
85
            }
86
        }
3✔
87
        return invalidatedNanopubs;
6✔
88
    }
89

90
    private static ReadsEnvironment ENV_READER = new ReadsEnvironment(System::getenv);
15✔
91

92
    /**
93
     * Set the environment reader (used for testing purposes).
94
     *
95
     * @param reader the environment reader to set
96
     */
97
    public static void setEnvReader(ReadsEnvironment reader) {
98
        ENV_READER = reader;
6✔
99
    }
3✔
100

101
    /**
102
     * Get an environment variable, returning a default value if not set.
103
     *
104
     * @param name         the name of the environment variable
105
     * @param defaultValue the default value to return if the variable is not set
106
     * @return the value of the environment variable, or the default value if not set
107
     */
108
    public static String getEnv(String name, String defaultValue) {
109
        logger.info("Retrieving environment variable: {}", name);
12✔
110
        String value = ENV_READER.getEnv(name);
12✔
111
        if (value == null) {
6✔
112
            value = defaultValue;
6✔
113
            logger.info("The variable: {} is not set. Using default value: {}", name, defaultValue);
15✔
114
        }
115
        return value;
6✔
116
    }
117

118
    /**
119
     * Get the type hash for a given type, recording it in the database if necessary.
120
     *
121
     * @param mongoSession the MongoDB client session
122
     * @param type         the type to get the hash for
123
     * @return the type hash
124
     */
125
    public static String getTypeHash(ClientSession mongoSession, Object type) {
126
        String typeHash = Utils.getHash(type.toString());
12✔
127
        if (type.toString().equals("$")) {
15✔
128
            typeHash = "$";
9✔
129
        } else {
130
            RegistryDB.recordHash(mongoSession, type.toString());
12✔
131
        }
132
        return typeHash;
6✔
133
    }
134

135
    /**
136
     * Get a label for an agent ID, truncating if necessary.
137
     *
138
     * @param agentId the agent ID
139
     * @return the agent label
140
     */
141
    public static String getAgentLabel(String agentId) {
142
        if (agentId == null || agentId.isBlank()) {
15✔
143
            throw new IllegalArgumentException("Agent ID cannot be null or blank");
15✔
144
        }
145
        agentId = agentId.replaceFirst("^https://orcid\\.org/", "orcid:");
15✔
146
        if (agentId.length() > 55) {
12✔
147
            return agentId.substring(0, 50) + "...";
18✔
148
        }
149
        return agentId;
6✔
150
    }
151

152
    /**
153
     * Check if the given status indicates an unloaded entry.
154
     *
155
     * @param status the status to check
156
     * @return true if the status indicates an unloaded entry, false otherwise
157
     */
158
    public static boolean isUnloadedStatus(String status) {
159
        if (status.equals(EntryStatus.seen.getValue())) return true;  // only exists in "accounts_loading"?
21✔
160
        return status.equals(EntryStatus.skipped.getValue());
15✔
161
    }
162

163
    /**
164
     * Check if the given status indicates a core loaded entry.
165
     *
166
     * @param status the status to check
167
     * @return true if the status indicates a core loaded entry, false otherwise
168
     */
169
    public static boolean isCoreLoadedStatus(String status) {
170
        if (status.equals(EntryStatus.visited.getValue())) return true;  // only exists in "accounts_loading"?
21✔
171
        if (status.equals(EntryStatus.expanded.getValue())) return true;  // only exists in "accounts_loading"?
21✔
172
        if (status.equals(EntryStatus.processed.getValue())) return true;  // only exists in "accounts_loading"?
21✔
173
        if (status.equals(EntryStatus.aggregated.getValue())) return true;  // only exists in "accounts_loading"?
21✔
174
        if (status.equals(EntryStatus.approved.getValue())) return true;  // only exists in "accounts_loading"?
21✔
175
        if (status.equals(EntryStatus.contested.getValue())) return true;
21✔
176
        if (status.equals(EntryStatus.toLoad.getValue())) return true;  // only exists in "accounts_loading"?
21✔
177
        return status.equals(EntryStatus.loaded.getValue());
15✔
178
    }
179

180
    /**
181
     * Check if the given status indicates a fully loaded entry.
182
     *
183
     * @param status the status to check
184
     * @return true if the status indicates a fully loaded entry, false otherwise
185
     */
186
    public static boolean isFullyLoadedStatus(String status) {
187
        return status.equals(EntryStatus.loaded.getValue());
15✔
188
    }
189

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

192
    public static final String TYPE_JSON = "application/json";
193
    public static final String TYPE_TRIG = "application/trig";
194
    public static final String TYPE_JELLY = "application/x-jelly-rdf";
195
    public static final String TYPE_JSONLD = "application/ld+json";
196
    public static final String TYPE_NQUADS = "application/n-quads";
197
    public static final String TYPE_TRIX = "application/trix";
198
    public static final String TYPE_HTML = "text/html";
199

200
    // Content types supported on a ListPage
201
    public static final String SUPPORTED_TYPES_LIST = TYPE_JSON + "," + TYPE_JELLY + "," + TYPE_HTML;
202
    // Content types supported on a NanopubPage
203
    public static final String SUPPORTED_TYPES_NANOPUB = TYPE_TRIG + "," + TYPE_JELLY + "," + TYPE_JSONLD + "," + TYPE_NQUADS + "," + TYPE_TRIX + "," + TYPE_HTML;
204

205
    private static Map<String, String> extensionTypeMap;
206

207
    /**
208
     * Get the type corresponding to a given file extension.
209
     *
210
     * @param extension the file extension
211
     * @return the corresponding MIME type, or null if not found
212
     */
213
    public static String getType(String extension) {
214
        if (extension == null) {
6✔
215
            return null;
6✔
216
        }
217
        if (extensionTypeMap == null) {
6✔
218
            extensionTypeMap = new HashMap<>();
12✔
219
            extensionTypeMap.put("trig", TYPE_TRIG);
15✔
220
            extensionTypeMap.put("jelly", TYPE_JELLY);
15✔
221
            extensionTypeMap.put("jsonld", TYPE_JSONLD);
15✔
222
            extensionTypeMap.put("nq", TYPE_NQUADS);
15✔
223
            extensionTypeMap.put("xml", TYPE_TRIX);
15✔
224
            extensionTypeMap.put("html", TYPE_HTML);
15✔
225
            extensionTypeMap.put("json", TYPE_JSON);
15✔
226
        }
227
        return extensionTypeMap.get(extension);
15✔
228
    }
229

230
    private static List<String> peerUrls;
231

232
    /**
233
     * Get the list of peer registry URLs.
234
     *
235
     * @return the list of peer registry URLs
236
     */
237
    public static List<String> getPeerUrls() {
238
        if (peerUrls == null) {
6✔
239
            peerUrls = new ArrayList<>();
12✔
240
            String envPeerUrls = getEnv("REGISTRY_PEER_URLS", "");
12✔
241
            String thisRegistryUrl = getEnv("REGISTRY_SERVICE_URL", "");
12✔
242
            if (!envPeerUrls.isEmpty()) {
9✔
243
                for (String peerUrl : envPeerUrls.split(";")) {
57✔
244
                    if (thisRegistryUrl.equals(peerUrl)) {
12!
245
                        continue;
×
246
                    }
247
                    peerUrls.add(peerUrl);
12✔
248
                }
249
            } else {
250
                NanopubSetting setting;
251
                try {
252
                    setting = getSetting();
6✔
253
                } catch (MalformedNanopubException | IOException ex) {
3✔
254
                    logger.error("Error loading registry setting: {}", ex.getMessage());
15✔
255
                    throw new RuntimeException(ex);
15✔
256
                }
3✔
257
                for (IRI iri : setting.getBootstrapServices()) {
33✔
258
                    String peerUrl = iri.stringValue();
9✔
259
                    if (thisRegistryUrl.equals(peerUrl)) {
12!
260
                        continue;
×
261
                    }
262
                    peerUrls.add(peerUrl);
12✔
263
                }
3✔
264
            }
265
        }
266
        return peerUrls;
6✔
267
    }
268

269
    private static volatile NanopubSetting settingNp;
270

271
    /**
272
     * Get the nanopublication setting.
273
     *
274
     * @return the nanopublication setting
275
     * @throws RDF4JException            if there is an error retrieving the setting
276
     * @throws MalformedNanopubException if the setting nanopublication is malformed
277
     * @throws IOException               if there is an I/O error
278
     */
279
    public static NanopubSetting getSetting() throws RDF4JException, MalformedNanopubException, IOException {
280
        if (settingNp == null) {
6✔
281
            synchronized (Utils.class) {
12✔
282
                if (settingNp == null) {
6!
283
                    String settingPath = getEnv("REGISTRY_SETTING_FILE", "/data/setting.trig");
12✔
284
                    settingNp = new NanopubSetting(new NanopubImpl(new File(settingPath)));
33✔
285
                }
286
            }
9✔
287
        }
288
        return settingNp;
6✔
289
    }
290

291
    /**
292
     * Get a random peer registry URL.
293
     *
294
     * @return a random peer registry URL
295
     * @throws RDF4JException if there is an error retrieving the peer URLs
296
     */
297
    public static String getRandomPeer() throws RDF4JException {
298
        List<String> peerUrls = getPeerUrls();
6✔
299
        return peerUrls.get(random.nextInt(peerUrls.size()));
24✔
300
    }
301

302
    private static final Random random = new Random();
12✔
303

304
    /**
305
     * Get the random number generator.
306
     *
307
     * @return the random number generator
308
     */
309
    public static Random getRandom() {
310
        return random;
6✔
311
    }
312

313
    private static final Gson g = new Gson();
12✔
314
    private static Type listType = new TypeToken<List<String>>() {
21✔
315
    }.getType();
6✔
316

317
    /**
318
     * Retrieve a list of strings from a JSON URL.
319
     *
320
     * @param url the URL to retrieve the JSON from
321
     * @return the list of strings
322
     * @throws JsonIOException     if there is an error reading the JSON
323
     * @throws JsonSyntaxException if the JSON syntax is invalid
324
     * @throws IOException         if there is an I/O error
325
     * @throws URISyntaxException  if the URL syntax is invalid
326
     */
327
    public static List<String> retrieveListFromJsonUrl(String url) throws JsonIOException, JsonSyntaxException, IOException, URISyntaxException {
328
        return g.fromJson(new InputStreamReader(new URI(url).toURL().openStream()), listType);
×
329
    }
330

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