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

Mutations

62

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

75

1.1
Location : buildContext
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/state/Context::setLogger → KILLED

77

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

107

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/web/FullSystem::initialize → KILLED

118

1.1
Location : start
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/web/FullSystem::createSystemRunningMarker → KILLED

121

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

141

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

144

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

155

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

163

1.1
Location : createSystemRunningMarker
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/utils/FileUtils::writeString → KILLED

164

1.1
Location : createSystemRunningMarker
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to java/io/File::deleteOnExit → KILLED

168

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

172

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

176

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

180

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

184

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

188

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

193

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

195

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

208

1.1
Location : closeCore
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/web/IServer::close → KILLED

210

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

212

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

253

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

272

1.1
Location : toString
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with "" for com/renomad/minum/web/FullSystem::toString → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0