| 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 → TIMED_OUT |
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 → TIMED_OUT |
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 → TIMED_OUT |
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 → TIMED_OUT |
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 | } | |
Mutations | ||
| 62 |
1.1 |
|
| 75 |
1.1 |
|
| 77 |
1.1 |
|
| 107 |
1.1 |
|
| 118 |
1.1 |
|
| 121 |
1.1 |
|
| 141 |
1.1 |
|
| 144 |
1.1 |
|
| 155 |
1.1 |
|
| 163 |
1.1 |
|
| 164 |
1.1 |
|
| 168 |
1.1 |
|
| 172 |
1.1 |
|
| 176 |
1.1 |
|
| 180 |
1.1 |
|
| 184 |
1.1 |
|
| 188 |
1.1 |
|
| 193 |
1.1 |
|
| 195 |
1.1 |
|
| 208 |
1.1 |
|
| 210 |
1.1 |
|
| 212 |
1.1 |
|
| 253 |
1.1 |