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

knowledgepixels / nanopub-query / 17671055880

12 Sep 2025 09:53AM UTC coverage: 75.992% (-0.7%) from 76.74%
17671055880

Pull #47

github

web-flow
Merge b5202ad8e into 9231a160b
Pull Request #47: Update nanopub java

230 of 318 branches covered (72.33%)

Branch coverage included in aggregate %.

574 of 740 relevant lines covered (77.57%)

3.91 hits per line

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

86.4
src/main/java/com/knowledgepixels/query/StatusController.java
1
package com.knowledgepixels.query;
2

3
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
4
import org.eclipse.rdf4j.model.Literal;
5
import org.eclipse.rdf4j.repository.RepositoryConnection;
6
import org.nanopub.vocabulary.NPA;
7

8
import java.util.Objects;
9

10
/**
11
 * Class to control the load status of the database.
12
 */
13
public class StatusController {
2✔
14

15
    /**
16
     * The load states in which the database can be.
17
     */
18
    public enum State {
3✔
19
        /**
20
         * The service is launching.
21
         */
22
        LAUNCHING,
6✔
23
        /**
24
         * The service is loading.
25
         */
26
        LOADING_INITIAL,
6✔
27
        /**
28
         * The service is loading updates.
29
         */
30
        LOADING_UPDATES,
6✔
31
        /**
32
         * The service is ready to serve requests.
33
         */
34
        READY,
6✔
35
    }
36

37
    /**
38
     * Get the singleton instance of the StatusController.
39
     *
40
     * @return the StatusController instance
41
     */
42
    public static StatusController get() {
43
        return instance;
2✔
44
    }
45

46
    private final static StatusController instance = new StatusController();
5✔
47

48
    private boolean initialized = false;
3✔
49
    private State state = null;
3✔
50
    private long lastCommittedCounter = -1;
4✔
51
    private RepositoryConnection adminRepoConn;
52

53
    /**
54
     * Represents the current status of the service, including the load counter.
55
     */
56
    public static class LoadingStatus {
57

58
        /**
59
         * The current state of the service.
60
         */
61
        public final State state;
62

63
        /**
64
         * The current load counter.
65
         */
66
        public final long loadCounter;
67

68
        private LoadingStatus(State state, long loadCounter) {
2✔
69
            this.state = state;
3✔
70
            this.loadCounter = loadCounter;
3✔
71
        }
1✔
72

73
        /**
74
         * Create a new LoadingStatus instance.
75
         *
76
         * @param state       the current state of the service
77
         * @param loadCounter the current load counter
78
         * @return a new LoadingStatus instance
79
         */
80
        public static LoadingStatus of(State state, long loadCounter) {
81
            return new LoadingStatus(state, loadCounter);
6✔
82
        }
83

84
        @Override
85
        public boolean equals(Object o) {
86
            if (o == null || getClass() != o.getClass()) {
7✔
87
                return false;
2✔
88
            }
89
            LoadingStatus that = (LoadingStatus) o;
3✔
90
            return loadCounter == that.loadCounter && state == that.state;
15✔
91
        }
92

93
        @Override
94
        public int hashCode() {
95
            return Objects.hash(state, loadCounter);
15✔
96
        }
97

98
    }
99

100
    /**
101
     * Initialize the StatusController, fetching the last known state from the DB.
102
     * This must be called right after service startup, before loading any nanopubs.
103
     *
104
     * @return the current state and the last committed counter
105
     */
106
    public LoadingStatus initialize() {
107
        synchronized (this) {
4✔
108
            if (initialized) {
3✔
109
                throw new IllegalStateException("Already initialized");
5✔
110
            }
111
            state = State.LAUNCHING;
3✔
112
            adminRepoConn = TripleStore.get().getAdminRepoConnection();
4✔
113
            // Serializable, as the service state needs to be strictly consistent
114
            adminRepoConn.begin(IsolationLevels.SERIALIZABLE);
4✔
115
            // Fetch the state from the DB
116
            try (var statements = adminRepoConn.getStatements(NPA.THIS_REPO, NPA.HAS_STATUS, null, NPA.GRAPH)) {
13✔
117
                if (!statements.hasNext()) {
3!
118
                    adminRepoConn.add(NPA.THIS_REPO, NPA.HAS_STATUS, stateAsLiteral(state), NPA.GRAPH);
16✔
119
                } else {
120
                    var stateStatement = statements.next();
×
121
                    state = State.valueOf(stateStatement.getObject().stringValue());
×
122
                }
123
            }
124
            // Fetch the load counter from the DB
125
            try (var statements = adminRepoConn.getStatements(NPA.THIS_REPO, NPA.HAS_REGISTRY_LOAD_COUNTER, null, NPA.GRAPH)) {
13✔
126
                if (!statements.hasNext()) {
3!
127
                    adminRepoConn.add(NPA.THIS_REPO, NPA.HAS_REGISTRY_LOAD_COUNTER, adminRepoConn.getValueFactory().createLiteral(-1L), NPA.GRAPH);
17✔
128
                } else {
129
                    var counterStatement = statements.next();
×
130
                    var stringVal = counterStatement.getObject().stringValue();
×
131
                    lastCommittedCounter = Long.parseLong(stringVal);
×
132
                }
133
                adminRepoConn.commit();
3✔
134
            } catch (Exception e) {
×
135
                if (adminRepoConn.isActive()) adminRepoConn.rollback();
×
136
                throw new RuntimeException(e);
×
137
            }
1✔
138
            initialized = true;
3✔
139
            return getState();
5✔
140
        }
141
    }
142

143
    /**
144
     * Get the current state of the service.
145
     *
146
     * @return the current state and the last committed counter
147
     */
148
    public LoadingStatus getState() {
149
        synchronized (this) {
4✔
150
            return LoadingStatus.of(state, lastCommittedCounter);
8✔
151
        }
152
    }
153

154
    /**
155
     * Transition the service to the LOADING_INITIAL state and update the load counter.
156
     * This should be called in two situations:
157
     * - By the main loading thread (after calling initialize()) to start loading the initial nanopubs.
158
     * - By the initial nanopub loader, as it processes the initial nanopubs.
159
     *
160
     * @param loadCounter the new load counter
161
     */
162
    public void setLoadingInitial(long loadCounter) {
163
        synchronized (this) {
4✔
164
            if (state != State.LAUNCHING && state != State.LOADING_INITIAL) {
8✔
165
                throw new IllegalStateException("Cannot transition to LOADING_INITIAL, as the " + "current state is " + state);
8✔
166
            }
167
            if (lastCommittedCounter > loadCounter) {
5✔
168
                throw new IllegalStateException("Cannot update the load counter from " + lastCommittedCounter + " to " + loadCounter);
8✔
169
            }
170
            updateState(State.LOADING_INITIAL, loadCounter);
4✔
171
        }
3✔
172
    }
1✔
173

174
    /**
175
     * Transition the service to the LOADING_UPDATES state and update the load counter.
176
     * This should be called by the updates loader, when it starts processing new nanopubs, or
177
     * when it finishes processing a batch of nanopubs.
178
     *
179
     * @param loadCounter the new load counter
180
     */
181
    public void setLoadingUpdates(long loadCounter) {
182
        synchronized (this) {
4✔
183
            if (state != State.LAUNCHING && state != State.LOADING_UPDATES && state != State.READY) {
12✔
184
                throw new IllegalStateException("Cannot transition to LOADING_UPDATES, as the " + "current state is " + state);
8✔
185
            }
186
            if (lastCommittedCounter > loadCounter) {
5✔
187
                throw new IllegalStateException("Cannot update the load counter from " + lastCommittedCounter + " to " + loadCounter);
8✔
188
            }
189
            updateState(State.LOADING_UPDATES, loadCounter);
4✔
190
        }
3✔
191
    }
1✔
192

193
    /**
194
     * Transition the service to the READY state.
195
     * This should be called by the loaders, after they finish their work.
196
     */
197
    public void setReady() {
198
        synchronized (this) {
4✔
199
            if (state != State.READY) {
4✔
200
                updateState(State.READY, lastCommittedCounter);
5✔
201
            }
202
        }
3✔
203
    }
1✔
204

205
    void updateState(State newState, long loadCounter) {
206
        synchronized (this) {
4✔
207
            try {
208
                // Serializable, as the service state needs to be strictly consistent
209
                adminRepoConn.begin(IsolationLevels.SERIALIZABLE);
4✔
210
                adminRepoConn.remove(NPA.THIS_REPO, NPA.HAS_STATUS, null, NPA.GRAPH);
12✔
211
                adminRepoConn.add(NPA.THIS_REPO, NPA.HAS_STATUS, stateAsLiteral(newState), NPA.GRAPH);
14✔
212
                adminRepoConn.remove(NPA.THIS_REPO, NPA.HAS_REGISTRY_LOAD_COUNTER, null, NPA.GRAPH);
12✔
213
                adminRepoConn.add(NPA.THIS_REPO, NPA.HAS_REGISTRY_LOAD_COUNTER, adminRepoConn.getValueFactory().createLiteral(loadCounter), NPA.GRAPH);
16✔
214
                adminRepoConn.commit();
3✔
215
                state = newState;
3✔
216
                lastCommittedCounter = loadCounter;
3✔
217
            } catch (Exception e) {
×
218
                if (adminRepoConn.isActive()) adminRepoConn.rollback();
×
219
                throw new RuntimeException(e);
×
220
            }
1✔
221
        }
3✔
222
    }
1✔
223

224
    private Literal stateAsLiteral(State s) {
225
        return adminRepoConn.getValueFactory().createLiteral(s.toString());
7✔
226
    }
227

228
    /**
229
     * Reset the StatusController for testing purposes.
230
     * This will clear the state and allow re-initialization.
231
     */
232
    void resetForTest() {
233
        synchronized (this) {
4✔
234
            initialized = false;
3✔
235
            state = null;
3✔
236
            lastCommittedCounter = -1;
3✔
237
            adminRepoConn = null;
3✔
238
        }
3✔
239
    }
1✔
240

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