TheBrig.java

1
package com.renomad.minum.security;
2
3
import com.renomad.minum.state.Constants;
4
import com.renomad.minum.state.Context;
5
import com.renomad.minum.database.Db;
6
import com.renomad.minum.logging.ILogger;
7
import com.renomad.minum.utils.SearchUtils;
8
import com.renomad.minum.utils.ThrowingRunnable;
9
import com.renomad.minum.utils.TimeUtils;
10
11
import java.util.*;
12
import java.util.concurrent.*;
13
import java.util.concurrent.locks.ReentrantLock;
14
15
/**
16
 * See {@link ITheBrig}
17
 */
18
public final class TheBrig implements ITheBrig {
19
    private final ExecutorService es;
20
    private final Db<Inmate> inmatesDb;
21
    private final ILogger logger;
22
    private final Constants constants;
23
    private final ReentrantLock lock = new ReentrantLock();
24
    private Thread myThread;
25
26
    /**
27
     * How long our inner thread will sleep before waking up to scan
28
     * for old keys
29
     */
30
    private final int sleepTime;
31
32
    public TheBrig(int sleepTime, Context context) {
33
        this.es = context.getExecutorService();
34
        this.constants = context.getConstants();
35
        this.logger = context.getLogger();
36
        this.inmatesDb = context.getDb("the_brig", Inmate.EMPTY);
37
        this.sleepTime = sleepTime;
38
    }
39
40
    /**
41
     * In this class we create a thread that runs throughout the lifetime
42
     * of the application, in an infinite loop removing keys from the list
43
     * under consideration.
44
     */
45
    public TheBrig(Context context) {
46
        this(10 * 1000, context);
47
    }
48
49
    // Regarding the BusyWait - indeed, we expect that the while loop
50
    // below is an infinite loop unless there's an exception thrown, that's what it is.
51
    @Override
52
    public ITheBrig initialize() {
53
        logger.logDebug(() -> "Initializing TheBrig main loop");
54
        ThrowingRunnable innerLoopThread = () -> {
55
            Thread.currentThread().setName("TheBrigThread");
56
            myThread = Thread.currentThread();
57
            while (true) {
58
                try {
59 1 1. lambda$initialize$2 : removed call to com/renomad/minum/security/TheBrig::reviewCurrentInmates → TIMED_OUT
                    reviewCurrentInmates();
60
                } catch (InterruptedException ex) {
61
62
                    /*
63
                    this is what we expect to happen.
64
                    once this happens, we just continue on.
65
                    this only gets called when we are trying to shut everything
66
                    down cleanly
67
                     */
68
69
                    logger.logDebug(() -> String.format("%s TheBrig is stopped.%n", TimeUtils.getTimestampIsoInstant()));
70
                    Thread.currentThread().interrupt();
71
                    break;
72
                }
73
            }
74
        };
75
        es.submit(ThrowingRunnable.throwingRunnableWrapper(innerLoopThread, logger));
76 1 1. initialize : replaced return value with null for com/renomad/minum/security/TheBrig::initialize → TIMED_OUT
        return this;
77
    }
78
79
    private void reviewCurrentInmates() throws InterruptedException {
80
        Collection<Inmate> values = inmatesDb.values();
81 1 1. reviewCurrentInmates : negated conditional → TIMED_OUT
        if (! values.isEmpty()) {
82
            logger.logTrace(() -> "TheBrig reviewing current inmates. Count: " + values.size());
83
        }
84
        var now = System.currentTimeMillis();
85
86 1 1. reviewCurrentInmates : removed call to com/renomad/minum/security/TheBrig::processInmateList → KILLED
        processInmateList(now, values, logger, inmatesDb);
87
        Thread.sleep(sleepTime);
88
    }
89
90
    /**
91
     * figure out which clients have paid their dues
92
     * @param now the current time, in milliseconds past the epoch
93
     * @param inmatesDb the database of all inmates
94
     */
95
    static void processInmateList(long now, Collection<Inmate> inmates, ILogger logger, Db<Inmate> inmatesDb) {
96
        List<String> keysToRemove = new ArrayList<>();
97
        for (Inmate clientKeyAndDuration : inmates) {
98 1 1. processInmateList : removed call to com/renomad/minum/security/TheBrig::reviewForParole → TIMED_OUT
            reviewForParole(now, keysToRemove, clientKeyAndDuration, logger);
99
        }
100
        for (var k : keysToRemove) {
101
            logger.logTrace(() -> "TheBrig: removing " + k + " from jail");
102 2 1. lambda$processInmateList$5 : replaced boolean return with true for com/renomad/minum/security/TheBrig::lambda$processInmateList$5 → TIMED_OUT
2. lambda$processInmateList$5 : replaced boolean return with false for com/renomad/minum/security/TheBrig::lambda$processInmateList$5 → KILLED
            Inmate inmateToRemove = SearchUtils.findExactlyOne(inmates.stream(), x -> x.getClientId().equals(k));
103 1 1. processInmateList : removed call to com/renomad/minum/database/Db::delete → KILLED
            inmatesDb.delete(inmateToRemove);
104
        }
105
    }
106
107
    private static void reviewForParole(
108
            long now,
109
            List<String> keysToRemove,
110
            Inmate inmate,
111
            ILogger logger) {
112
        // if the release time is in the past (that is, the release time is
113
        // before / less-than now), add them to the list to be released.
114 2 1. reviewForParole : changed conditional boundary → TIMED_OUT
2. reviewForParole : negated conditional → KILLED
        if (inmate.getReleaseTime() < now) {
115
            logger.logTrace(() -> "UnderInvestigation: " + inmate.getClientId() + " has paid its dues as of " + inmate.getReleaseTime() + " and is getting released. Current time: " + now);
116
            keysToRemove.add(inmate.getClientId());
117
        }
118
    }
119
120
    @Override
121
    public void stop() {
122
        logger.logDebug(() -> "TheBrig has been told to stop");
123 1 1. stop : negated conditional → KILLED
        if (myThread != null) {
124
            logger.logDebug(() -> "TheBrig: Sending interrupt to thread");
125
            myThread.interrupt();
126 1 1. stop : removed call to com/renomad/minum/database/Db::stop → KILLED
            this.inmatesDb.stop();
127
        } else {
128
            throw new MinumSecurityException("TheBrig was told to stop, but it was uninitialized");
129
        }
130
    }
131
132
    @Override
133
    public boolean sendToJail(String clientIdentifier, long sentenceDuration) {
134 1 1. sendToJail : negated conditional → KILLED
        if (!constants.isTheBrigEnabled) {
135 1 1. sendToJail : replaced boolean return with true for com/renomad/minum/security/TheBrig::sendToJail → KILLED
            return false;
136
        }
137 1 1. sendToJail : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
        lock.lock(); // block threads here if multiple are trying to get in - only one gets in at a time
138
        try {
139
            long now = System.currentTimeMillis();
140
141 2 1. lambda$sendToJail$9 : replaced boolean return with true for com/renomad/minum/security/TheBrig::lambda$sendToJail$9 → KILLED
2. lambda$sendToJail$9 : replaced boolean return with false for com/renomad/minum/security/TheBrig::lambda$sendToJail$9 → KILLED
            Inmate existingInmate = SearchUtils.findExactlyOne(inmatesDb.values().stream(), x -> x.getClientId().equals(clientIdentifier));
142 1 1. sendToJail : negated conditional → KILLED
            if (existingInmate == null) {
143
                // if this is a new inmate, add them
144 1 1. sendToJail : Replaced long addition with subtraction → KILLED
                long releaseTime = now + sentenceDuration;
145
                logger.logDebug(() -> "TheBrig: Putting away " + clientIdentifier + " for " + sentenceDuration + " milliseconds. Release time: " + releaseTime + ". Current time: " + now);
146
                Inmate newInmate = new Inmate(0L, clientIdentifier, releaseTime);
147
                inmatesDb.write(newInmate);
148
            } else {
149
                // if this is an existing inmate continuing to attack us, just update their duration
150 1 1. sendToJail : Replaced long addition with subtraction → KILLED
                long releaseTime = existingInmate.getReleaseTime() + sentenceDuration;
151
                logger.logDebug(() -> "TheBrig: Putting away " + clientIdentifier + " for " + sentenceDuration + " milliseconds. Release time: " + releaseTime + ". Current time: " + now);
152
                inmatesDb.write(new Inmate(existingInmate.getIndex(), existingInmate.getClientId(), releaseTime));
153
            }
154
        } finally {
155 1 1. sendToJail : removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED
            lock.unlock();
156
        }
157 1 1. sendToJail : replaced boolean return with false for com/renomad/minum/security/TheBrig::sendToJail → KILLED
        return true;
158
159
    }
160
161
    @Override
162
    public boolean isInJail(String clientIdentifier) {
163 1 1. isInJail : negated conditional → KILLED
        if (!constants.isTheBrigEnabled) {
164 1 1. isInJail : replaced boolean return with true for com/renomad/minum/security/TheBrig::isInJail → KILLED
            return false;
165
        }
166 1 1. isInJail : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
        lock.lock();
167
        try {
168 4 1. isInJail : replaced boolean return with true for com/renomad/minum/security/TheBrig::isInJail → TIMED_OUT
2. isInJail : replaced boolean return with false for com/renomad/minum/security/TheBrig::isInJail → KILLED
3. lambda$isInJail$12 : replaced boolean return with false for com/renomad/minum/security/TheBrig::lambda$isInJail$12 → KILLED
4. lambda$isInJail$12 : replaced boolean return with true for com/renomad/minum/security/TheBrig::lambda$isInJail$12 → KILLED
            return inmatesDb.values().stream().anyMatch(x -> x.getClientId().equals(clientIdentifier));
169
        } finally {
170 1 1. isInJail : removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED
            lock.unlock();
171
        }
172
    }
173
174
    @Override
175
    public List<Inmate> getInmates() {
176 1 1. getInmates : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
        lock.lock();
177
        try {
178 1 1. getInmates : replaced return value with Collections.emptyList for com/renomad/minum/security/TheBrig::getInmates → KILLED
            return inmatesDb.values().stream().toList();
179
        } finally {
180 1 1. getInmates : removed call to java/util/concurrent/locks/ReentrantLock::unlock → SURVIVED
            lock.unlock();
181
        }
182
    }
183
184
}

Mutations

59

1.1
Location : lambda$initialize$2
Killed by : none
removed call to com/renomad/minum/security/TheBrig::reviewCurrentInmates → TIMED_OUT

76

1.1
Location : initialize
Killed by : none
replaced return value with null for com/renomad/minum/security/TheBrig::initialize → TIMED_OUT

81

1.1
Location : reviewCurrentInmates
Killed by : none
negated conditional → TIMED_OUT

86

1.1
Location : reviewCurrentInmates
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/security/TheBrig::processInmateList → KILLED

98

1.1
Location : processInmateList
Killed by : none
removed call to com/renomad/minum/security/TheBrig::reviewForParole → TIMED_OUT

102

1.1
Location : lambda$processInmateList$5
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem(com.renomad.minum.web.FullSystemTests)
replaced boolean return with false for com/renomad/minum/security/TheBrig::lambda$processInmateList$5 → KILLED

2.2
Location : lambda$processInmateList$5
Killed by : none
replaced boolean return with true for com/renomad/minum/security/TheBrig::lambda$processInmateList$5 → TIMED_OUT

103

1.1
Location : processInmateList
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/database/Db::delete → KILLED

114

1.1
Location : reviewForParole
Killed by : none
changed conditional boundary → TIMED_OUT

2.2
Location : reviewForParole
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

123

1.1
Location : stop
Killed by : com.renomad.minum.security.TheBrigTests
negated conditional → KILLED

126

1.1
Location : stop
Killed by : com.renomad.minum.security.TheBrigTests
removed call to com/renomad/minum/database/Db::stop → KILLED

134

1.1
Location : sendToJail
Killed by : com.renomad.minum.security.TheBrigTests
negated conditional → KILLED

135

1.1
Location : sendToJail
Killed by : com.renomad.minum.security.TheBrigTests
replaced boolean return with true for com/renomad/minum/security/TheBrig::sendToJail → KILLED

137

1.1
Location : sendToJail
Killed by : com.renomad.minum.security.TheBrigTests
removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED

141

1.1
Location : lambda$sendToJail$9
Killed by : com.renomad.minum.security.TheBrigTests
replaced boolean return with true for com/renomad/minum/security/TheBrig::lambda$sendToJail$9 → KILLED

2.2
Location : lambda$sendToJail$9
Killed by : com.renomad.minum.security.TheBrigTests
replaced boolean return with false for com/renomad/minum/security/TheBrig::lambda$sendToJail$9 → KILLED

142

1.1
Location : sendToJail
Killed by : com.renomad.minum.security.TheBrigTests
negated conditional → KILLED

144

1.1
Location : sendToJail
Killed by : com.renomad.minum.security.TheBrigTests
Replaced long addition with subtraction → KILLED

150

1.1
Location : sendToJail
Killed by : com.renomad.minum.security.TheBrigTests
Replaced long addition with subtraction → KILLED

155

1.1
Location : sendToJail
Killed by : com.renomad.minum.security.TheBrigTests
removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED

157

1.1
Location : sendToJail
Killed by : com.renomad.minum.web.WebTests
replaced boolean return with false for com/renomad/minum/security/TheBrig::sendToJail → KILLED

163

1.1
Location : isInJail
Killed by : com.renomad.minum.web.WebPerformanceTests.test2(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

164

1.1
Location : isInJail
Killed by : com.renomad.minum.security.TheBrigTests
replaced boolean return with true for com/renomad/minum/security/TheBrig::isInJail → KILLED

166

1.1
Location : isInJail
Killed by : com.renomad.minum.web.WebPerformanceTests.test2(com.renomad.minum.web.WebPerformanceTests)
removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED

168

1.1
Location : isInJail
Killed by : none
replaced boolean return with true for com/renomad/minum/security/TheBrig::isInJail → TIMED_OUT

2.2
Location : isInJail
Killed by : com.renomad.minum.web.WebPerformanceTests.test2(com.renomad.minum.web.WebPerformanceTests)
replaced boolean return with false for com/renomad/minum/security/TheBrig::isInJail → KILLED

3.3
Location : lambda$isInJail$12
Killed by : com.renomad.minum.security.TheBrigTests
replaced boolean return with false for com/renomad/minum/security/TheBrig::lambda$isInJail$12 → KILLED

4.4
Location : lambda$isInJail$12
Killed by : com.renomad.minum.security.TheBrigTests
replaced boolean return with true for com/renomad/minum/security/TheBrig::lambda$isInJail$12 → KILLED

170

1.1
Location : isInJail
Killed by : com.renomad.minum.web.WebPerformanceTests.test2(com.renomad.minum.web.WebPerformanceTests)
removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED

176

1.1
Location : getInmates
Killed by : com.renomad.minum.security.TheBrigTests
removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED

178

1.1
Location : getInmates
Killed by : com.renomad.minum.security.TheBrigTests
replaced return value with Collections.emptyList for com/renomad/minum/security/TheBrig::getInmates → KILLED

180

1.1
Location : getInmates
Killed by : none
removed call to java/util/concurrent/locks/ReentrantLock::unlock → SURVIVED
Covering tests

Active mutators

Tests examined


Report generated by PIT 1.17.0