FullSystem.java

1
package com.renomad.minum.web;
2
3
import com.renomad.minum.queue.ActionQueueKiller;
4
import com.renomad.minum.state.Constants;
5
import com.renomad.minum.logging.ILogger;
6
import com.renomad.minum.logging.Logger;
7
import com.renomad.minum.security.ITheBrig;
8
import com.renomad.minum.security.TheBrig;
9
import com.renomad.minum.state.Context;
10
import com.renomad.minum.utils.*;
11
12
import java.io.IOException;
13
import java.nio.file.Path;
14
import java.nio.file.StandardOpenOption;
15
import java.time.ZoneId;
16
import java.time.ZonedDateTime;
17
import java.util.concurrent.CancellationException;
18
import java.util.concurrent.ExecutionException;
19
import java.util.concurrent.ExecutorService;
20
import java.util.concurrent.Executors;
21
22
/**
23
 * This class is responsible for instantiating necessary classes
24
 * for a valid system, in the proper order.
25
 * @see #initialize()
26
 * @see #start()
27
 */
28
public final class FullSystem {
29
30
    final ILogger logger;
31
    private final Constants constants;
32
    private final IFileUtils fileUtils;
33
    private IServer server;
34
    private WebFramework webFramework;
35
    private IServer sslServer;
36
    Thread shutdownHook;
37
    private ITheBrig theBrig;
38
    final ExecutorService es;
39
    private WebEngine webEngine;
40
41
    /**
42
     * This flag gives us some control if we need
43
     * to call {@link #shutdown()} manually, so close()
44
     * doesn't get run again when the shutdownHook
45
     * tries calling it.  This is primarily an issue just during
46
     * testing.
47
     */
48
    private boolean hasShutdown;
49
50
    private final Context context;
51
52
    /**
53
     * This constructor requires a {@link Context} object,
54
     * but it is easier and recommended to use {@link #initialize()}
55
     * instead.
56
     */
57
    public FullSystem(Context context) {
58
        this.logger = context.getLogger();
59
        this.constants = context.getConstants();
60
        this.fileUtils = new FileUtils(logger, constants);
61
        this.es = context.getExecutorService();
62
        this.context = context;
63 1 1. <init> : removed call to com/renomad/minum/state/Context::setFullSystem → KILLED
        context.setFullSystem(this);
64
    }
65
66
    /**
67
     * Builds a context object that is appropriate as a
68
     * parameter to constructing a {@link FullSystem}
69
     */
70
    public static Context buildContext() {
71
        var constants = new Constants();
72
        var executorService = Executors.newVirtualThreadPerTaskExecutor();
73
        var logger = new Logger(constants, executorService, "primary logger");
74
75 1 1. buildContext : replaced return value with null for com/renomad/minum/web/FullSystem::buildContext → KILLED
        return new Context(executorService, constants, logger);
76
    }
77
78
    /**
79
     * This is the typical entry point for system instantiation.  It will build
80
     * a {@link Context} object for you, and then properly instantiates the {@link FullSystem}.
81
     * <p>
82
     *     <em>Here is an example of a simple Main file using this method:</em>
83
     * </p>
84
     * <pre>{@code
85
     *   package org.example;
86
     *
87
     *   import com.renomad.minum.web.FullSystem;
88
     *   import com.renomad.minum.web.Response;
89
     *
90
     *   import static com.renomad.minum.web.RequestLine.Method.GET;
91
     *
92
     *   public class Main {
93
     *
94
     *       public static void main(String[] args) {
95
     *           FullSystem fs = FullSystem.initialize();
96
     *           fs.getWebFramework().registerPath(GET, "", request -> Response.htmlOk("<p>Hi there world!</p>"));
97
     *           fs.block();
98
     *       }
99
     *   }
100
     * }</pre>
101
     */
102
    public static FullSystem initialize() {
103
        var context = buildContext();
104
        var fullSystem = new FullSystem(context);
105 1 1. initialize : replaced return value with null for com/renomad/minum/web/FullSystem::initialize → KILLED
        return fullSystem.start();
106
    }
107
108
    /**
109
     * This method runs the necessary methods for starting the Minum
110
     * web server.  It is unlikely you will want to use this, unless you
111
     * require it for more control in testing.
112
     * @see #initialize()
113
     */
114
    public FullSystem start() {
115 1 1. start : removed call to com/renomad/minum/web/FullSystem::createSystemRunningFile → TIMED_OUT
        createSystemRunningFile(constants.enableSystemRunningMarker, logger, fileUtils);
116
117
        // set up an action to take place if the user shuts us down
118 1 1. start : removed call to com/renomad/minum/web/FullSystem::addShutdownHook → KILLED
        addShutdownHook();
119
120
        // instantiate our security code
121 1 1. start : negated conditional → KILLED
        if (constants.isTheBrigEnabled) {
122
            theBrig = new TheBrig(context).initialize();
123
        } else {
124
            theBrig = null;
125
        }
126
127
        // the web framework handles the HTTP communications
128
        webFramework = new WebFramework(context);
129
130
        // kick off the servers - low level internet handlers
131
        webEngine = new WebEngine(context, webFramework);
132
        server = webEngine.startServer();
133
        sslServer = webEngine.startSslServer();
134
135
        // document how long it took to start up the system
136
        var now = ZonedDateTime.now(ZoneId.of("UTC"));
137
        var nowMillis = now.toInstant().toEpochMilli();
138 1 1. start : Replaced long subtraction with addition → KILLED
        var startupTime = nowMillis - constants.startTime;
139
        logger.logDebug(() -> " *** Minum has finished primary startup after " + startupTime + " milliseconds ***");
140 1 1. start : replaced return value with null for com/renomad/minum/web/FullSystem::start → TIMED_OUT
        return this;
141
    }
142
143
    static void createSystemRunningFile(boolean enableSystemRunningMarker, ILogger logger, IFileUtils fileUtils) {
144
        // create a file in our current working directory to indicate we are running
145 1 1. createSystemRunningFile : negated conditional → TIMED_OUT
        if (enableSystemRunningMarker) {
146
            try {
147
                logger.logDebug(() -> "Writing a file to disk, SYSTEM_RUNNING, as a record that Minum is currently running");
148 1 1. createSystemRunningFile : negated conditional → KILLED
                if (fileUtils.exists(Path.of("SYSTEM_RUNNING"))) {
149
                    logger.logWarn(() -> "the SYSTEM_RUNNING file existed when we started Minum. This should not happen unless Minum was crash-closed before.");
150
                } else {
151 1 1. createSystemRunningFile : removed call to com/renomad/minum/utils/IFileUtils::writeString → KILLED
                    fileUtils.writeString(Path.of("SYSTEM_RUNNING"), "This file serves as a marker to indicate the system is running.\n", StandardOpenOption.CREATE_NEW);
152
                }
153
154
            } catch (IOException e) {
155
                throw new RuntimeException(e);
156
            }
157
        }
158
    }
159
160
    /**
161
     * this adds a hook to the Java runtime, so that if the app is running
162
     * and a user stops it - by pressing ctrl+c or a unix "kill" command - the
163
     * server socket will be shutdown and some messages about closing the server
164
     * will log
165
     */
166
    private void addShutdownHook() {
167
        shutdownHook = new Thread(ThrowingRunnable.throwingRunnableWrapper(this::shutdown, logger));
168 1 1. addShutdownHook : removed call to java/lang/Runtime::addShutdownHook → KILLED
        Runtime.getRuntime().addShutdownHook(shutdownHook);
169
    }
170
171
    /**
172
     * Returns current information on the non-encrypted server
173
     */
174
    public IServer getServer() {
175 1 1. getServer : replaced return value with null for com/renomad/minum/web/FullSystem::getServer → KILLED
        return server;
176
    }
177
178
    /**
179
     * Returns current information on the encrypted server
180
     */
181
    public IServer getSslServer() {
182 1 1. getSslServer : replaced return value with null for com/renomad/minum/web/FullSystem::getSslServer → KILLED
        return sslServer;
183
    }
184
185
    /**
186
     * A convenience method to get the instance registered for this property
187
     */
188
    public WebFramework getWebFramework() {
189 1 1. getWebFramework : replaced return value with null for com/renomad/minum/web/FullSystem::getWebFramework → TIMED_OUT
        return webFramework;
190
    }
191
192
    /**
193
     * A convenience method to get the instance registered for this property
194
     */
195
    public ITheBrig getTheBrig() {
196 1 1. getTheBrig : replaced return value with null for com/renomad/minum/web/FullSystem::getTheBrig → KILLED
        return theBrig;
197
    }
198
199
    /**
200
     * A convenience function to get the {@link Context} used in
201
     * this class's construction
202
     */
203
    public Context getContext() {
204 1 1. getContext : replaced return value with null for com/renomad/minum/web/FullSystem::getContext → TIMED_OUT
        return context;
205
    }
206
207
    WebEngine getWebEngine() {
208 1 1. getWebEngine : replaced return value with null for com/renomad/minum/web/FullSystem::getWebEngine → KILLED
        return webEngine;
209
    }
210
211
    /**
212
     * Shut down the system, set up to be run by a hook during initialization
213
     */
214
    public void shutdown() throws IOException {
215
216 1 1. shutdown : negated conditional → KILLED
        if (!hasShutdown) {
217
            logger.logTrace(() -> "close called on " + this);
218 1 1. shutdown : removed call to com/renomad/minum/web/FullSystem::closeCore → KILLED
            closeCore(logger, context, server, sslServer, this.toString(), fileUtils);
219
            hasShutdown = true;
220
        }
221
    }
222
223
    /**
224
     * The core code for closing resources
225
     * @param fullSystemName the name of this FullSystem, in cases where several are running concurrently
226
     */
227
    static void closeCore(ILogger logger, Context context, IServer server, IServer sslServer, String fullSystemName, IFileUtils fileUtils) throws IOException {
228
        logger.logDebug(() -> "Received shutdown command");
229
230 1 1. closeCore : negated conditional → TIMED_OUT
        if (server != null) {
231
            logger.logDebug(() -> " Stopping the server: " + server);
232 1 1. closeCore : removed call to com/renomad/minum/web/IServer::close → TIMED_OUT
            server.close();
233
        }
234
235 1 1. closeCore : negated conditional → KILLED
        if (sslServer != null) {
236
            logger.logDebug(() -> " Stopping the SSL server: " + server);
237 1 1. closeCore : removed call to com/renomad/minum/web/IServer::close → TIMED_OUT
            sslServer.close();
238
        }
239
240
        logger.logDebug(() -> "Killing all the action queues: " + context.getActionQueueState().aqQueueAsString());
241 1 1. closeCore : removed call to com/renomad/minum/queue/ActionQueueKiller::killAllQueues → KILLED
        new ActionQueueKiller(context).killAllQueues();
242
243
        logger.logDebug(() -> "Deleting SYSTEM_RUNNING file, indicating Minum is no longer running");
244
        fileUtils.deleteIfExists(Path.of("SYSTEM_RUNNING"));
245
246
        logger.logDebug(() -> String.format(
247
                "%s %s says: Goodbye world!%n", TimeUtils.getTimestampIsoInstant(), fullSystemName));
248
    }
249
250
    /**
251
     * A blocking call for our multi-threaded application.
252
     * <p>
253
     * This method is needed because the entire application is
254
     * multi-threaded.  Let me help contextualize the problem
255
     * for you:
256
     * </p>
257
     * <p>
258
     *     For this application, multi-threaded means that we
259
     *     are wrapping our code in {@link Thread} classes and
260
     *     having them run using a {@link ExecutorService}.  It's
261
     *     sort of like giving instructions to someone else to carry
262
     *     out the work and sending them away, trusting the work will
263
     *     get done, rather than doing it yourself.
264
     * </p>
265
     * <p>
266
     *     But, since our entire system is done this way, once we
267
     *     have sent all our threads on their way, there's nothing
268
     *     left for us to do! Continuing the analogy, it is like
269
     *     our whole job is to give other people instructions, and
270
     *     then just wait for them to return.
271
     * </p>
272
     * <p>
273
     *     That's the purpose of this method.  It's to wait for
274
     *     the return.
275
     * </p>
276
     * <p>
277
     *     It's probably best to call this method as one of the
278
     *     last statements in the main method, so it is clear where
279
     *     execution is blocking.
280
     * </p>
281
     */
282
    public void block() {
283 1 1. block : removed call to com/renomad/minum/web/FullSystem::blockCore → KILLED
        blockCore(this.server, this.sslServer, this.logger);
284
    }
285
286
    static void blockCore(IServer server, IServer sslServer, ILogger logger) {
287
        try {
288 1 1. blockCore : negated conditional → KILLED
            if (server != null) {
289
                logger.logTrace(() -> "%s thread has properly finished startup, now waiting on the result of %s".formatted(Thread.currentThread(), server));
290
                server.getCentralLoopFuture().get();
291
            }
292 1 1. blockCore : negated conditional → KILLED
            if (sslServer != null) {
293
                logger.logTrace(() -> "%s thread has properly finished startup, now waiting on the result of %s".formatted(Thread.currentThread(), sslServer));
294
                sslServer.getCentralLoopFuture().get();
295
            }
296
        } catch (InterruptedException | ExecutionException | CancellationException ex) {
297
            Thread.currentThread().interrupt();
298
            throw new WebServerException(ex);
299
        }
300
    }
301
302
}

Mutations

63

1.1
Location : <init>
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
removed call to com/renomad/minum/state/Context::setFullSystem → KILLED

75

1.1
Location : buildContext
Killed by : com.renomad.minum.web.FullSystemTests
replaced return value with null for com/renomad/minum/web/FullSystem::buildContext → KILLED

105

1.1
Location : initialize
Killed by : com.renomad.minum.web.FullSystemTests
replaced return value with null for com/renomad/minum/web/FullSystem::initialize → KILLED

115

1.1
Location : start
Killed by : none
removed call to com/renomad/minum/web/FullSystem::createSystemRunningFile → TIMED_OUT

118

1.1
Location : start
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
removed call to com/renomad/minum/web/FullSystem::addShutdownHook → KILLED

121

1.1
Location : start
Killed by : com.renomad.minum.FunctionalTests.test_EdgeCase_IOExceptionThrown_WebFramework(com.renomad.minum.FunctionalTests)
negated conditional → KILLED

138

1.1
Location : start
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
Replaced long subtraction with addition → KILLED

140

1.1
Location : start
Killed by : none
replaced return value with null for com/renomad/minum/web/FullSystem::start → TIMED_OUT

145

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

148

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

151

1.1
Location : createSystemRunningFile
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
removed call to com/renomad/minum/utils/IFileUtils::writeString → KILLED

168

1.1
Location : addShutdownHook
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
removed call to java/lang/Runtime::addShutdownHook → KILLED

175

1.1
Location : getServer
Killed by : com.renomad.minum.web.FullSystemTests
replaced return value with null for com/renomad/minum/web/FullSystem::getServer → KILLED

182

1.1
Location : getSslServer
Killed by : com.renomad.minum.web.FullSystemTests
replaced return value with null for com/renomad/minum/web/FullSystem::getSslServer → KILLED

189

1.1
Location : getWebFramework
Killed by : none
replaced return value with null for com/renomad/minum/web/FullSystem::getWebFramework → TIMED_OUT

196

1.1
Location : getTheBrig
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
replaced return value with null for com/renomad/minum/web/FullSystem::getTheBrig → KILLED

204

1.1
Location : getContext
Killed by : none
replaced return value with null for com/renomad/minum/web/FullSystem::getContext → TIMED_OUT

208

1.1
Location : getWebEngine
Killed by : com.renomad.minum.web.FullSystemTests
replaced return value with null for com/renomad/minum/web/FullSystem::getWebEngine → KILLED

216

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

218

1.1
Location : shutdown
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
removed call to com/renomad/minum/web/FullSystem::closeCore → KILLED

230

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

232

1.1
Location : closeCore
Killed by : none
removed call to com/renomad/minum/web/IServer::close → TIMED_OUT

235

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

237

1.1
Location : closeCore
Killed by : none
removed call to com/renomad/minum/web/IServer::close → TIMED_OUT

241

1.1
Location : closeCore
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
removed call to com/renomad/minum/queue/ActionQueueKiller::killAllQueues → KILLED

283

1.1
Location : block
Killed by : com.renomad.minum.web.FullSystemTests
removed call to com/renomad/minum/web/FullSystem::blockCore → KILLED

288

1.1
Location : blockCore
Killed by : com.renomad.minum.web.FullSystemTests
negated conditional → KILLED

292

1.1
Location : blockCore
Killed by : com.renomad.minum.web.FullSystemTests
negated conditional → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0