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 → KILLED
        return this;
77
    }
78
79
    private void reviewCurrentInmates() throws InterruptedException {
80
        Collection<Inmate> values = inmatesDb.values();
81 1 1. reviewCurrentInmates : negated conditional → KILLED
        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 → KILLED
            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 false for com/renomad/minum/security/TheBrig::lambda$processInmateList$5 → KILLED
2. lambda$processInmateList$5 : replaced boolean return with true 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 → SURVIVED
            this.inmatesDb.stop();
127
        } else {
128
            throw new MinumSecurityException("TheBrig was told to stop, but it was uninitialized");
129
        }
130
    }
131
132
133
    @Override
134
    public boolean sendToJail(String clientIdentifier, long sentenceDuration) {
135 1 1. sendToJail : negated conditional → KILLED
        if (!constants.isTheBrigEnabled) {
136 1 1. sendToJail : replaced boolean return with true for com/renomad/minum/security/TheBrig::sendToJail → KILLED
            return false;
137
        }
138 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
139
        try {
140
            long now = System.currentTimeMillis();
141
142 2 1. lambda$sendToJail$9 : replaced boolean return with true for com/renomad/minum/security/TheBrig::lambda$sendToJail$9 → SURVIVED
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));
143 1 1. sendToJail : negated conditional → KILLED
            if (existingInmate == null) {
144
                // if this is a new inmate, add them
145 1 1. sendToJail : Replaced long addition with subtraction → KILLED
                long releaseTime = now + sentenceDuration;
146
                logger.logDebug(() -> "TheBrig: Putting away " + clientIdentifier + " for " + sentenceDuration + " milliseconds. Release time: " + releaseTime + ". Current time: " + now);
147
                Inmate newInmate = new Inmate(0L, clientIdentifier, releaseTime);
148
                inmatesDb.write(newInmate);
149
            } else {
150
                // if this is an existing inmate continuing to attack us, just update their duration
151 1 1. sendToJail : Replaced long addition with subtraction → KILLED
                long releaseTime = existingInmate.getReleaseTime() + sentenceDuration;
152
                logger.logDebug(() -> "TheBrig: Putting away " + clientIdentifier + " for " + sentenceDuration + " milliseconds. Release time: " + releaseTime + ". Current time: " + now);
153
                inmatesDb.write(new Inmate(existingInmate.getIndex(), existingInmate.getClientId(), releaseTime));
154
            }
155
        } finally {
156 1 1. sendToJail : removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED
            lock.unlock();
157
        }
158 1 1. sendToJail : replaced boolean return with false for com/renomad/minum/security/TheBrig::sendToJail → KILLED
        return true;
159
160
    }
161
162
    @Override
163
    public boolean isInJail(String clientIdentifier) {
164 1 1. isInJail : negated conditional → KILLED
        if (!constants.isTheBrigEnabled) {
165 1 1. isInJail : replaced boolean return with true for com/renomad/minum/security/TheBrig::isInJail → KILLED
            return false;
166
        }
167 1 1. isInJail : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
        lock.lock();
168
        try {
169 4 1. lambda$isInJail$12 : replaced boolean return with true for com/renomad/minum/security/TheBrig::lambda$isInJail$12 → TIMED_OUT
2. isInJail : replaced boolean return with true for com/renomad/minum/security/TheBrig::isInJail → KILLED
3. isInJail : replaced boolean return with false for com/renomad/minum/security/TheBrig::isInJail → KILLED
4. lambda$isInJail$12 : replaced boolean return with false for com/renomad/minum/security/TheBrig::lambda$isInJail$12 → KILLED
            return inmatesDb.values().stream().anyMatch(x -> x.getClientId().equals(clientIdentifier));
170
        } finally {
171 1 1. isInJail : removed call to java/util/concurrent/locks/ReentrantLock::unlock → KILLED
            lock.unlock();
172
        }
173
    }
174
175
    @Override
176
    public List<Inmate> getInmates() {
177 1 1. getInmates : removed call to java/util/concurrent/locks/ReentrantLock::lock → KILLED
        lock.lock();
178
        try {
179 1 1. getInmates : replaced return value with Collections.emptyList for com/renomad/minum/security/TheBrig::getInmates → KILLED
            return inmatesDb.values().stream().toList();
180
        } finally {
181 1 1. getInmates : removed call to java/util/concurrent/locks/ReentrantLock::unlock → SURVIVED
            lock.unlock();
182
        }
183
    }
184
185
}

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 : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with null for com/renomad/minum/security/TheBrig::initialize → KILLED

81

1.1
Location : reviewCurrentInmates
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

86

1.1
Location : reviewCurrentInmates
Killed by : com.renomad.minum.security.TheBrigTests
removed call to com/renomad/minum/security/TheBrig::processInmateList → KILLED

98

1.1
Location : processInmateList
Killed by : com.renomad.minum.security.TheBrigTests
removed call to com/renomad/minum/security/TheBrig::reviewForParole → KILLED

102

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

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

103

1.1
Location : processInmateList
Killed by : com.renomad.minum.security.TheBrigTests
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 : none
removed call to com/renomad/minum/database/Db::stop → SURVIVED
Covering tests

135

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

136

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

138

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

142

1.1
Location : lambda$sendToJail$9
Killed by : none
replaced boolean return with true for com/renomad/minum/security/TheBrig::lambda$sendToJail$9 → SURVIVED
Covering tests

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

143

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

145

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

151

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

156

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

158

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

164

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

165

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

167

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

169

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

2.2
Location : isInJail
Killed by : com.renomad.minum.security.TheBrigTests
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 : none
replaced boolean return with true for com/renomad/minum/security/TheBrig::lambda$isInJail$12 → TIMED_OUT

171

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

177

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

179

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

181

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