• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nats-io / java-nats-server-runner / #15

11 Nov 2023 05:13PM UTC coverage: 87.993% (+1.3%) from 86.693%
#15

push

github

web-flow
Mapped ports, better (#30)

61 of 63 new or added lines in 3 files covered. (96.83%)

2 existing lines in 1 file now uncovered.

491 of 558 relevant lines covered (87.99%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.01
/src/main/java/nats/io/NatsServerRunner.java
1
// Copyright 2020-2023 The NATS Authors
2
// Licensed under the Apache License, Version 2.0 (the "License");
3
// you may not use this file except in compliance with the License.
4
// You may obtain a copy of the License at:
5
//
6
// http://www.apache.org/licenses/LICENSE-2.0
7
//
8
// Unless required by applicable law or agreed to in writing, software
9
// distributed under the License is distributed on an "AS IS" BASIS,
10
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
// See the License for the specific language governing permissions and
12
// limitations under the License.
13

14
package nats.io;
15

16
import java.io.*;
17
import java.net.ConnectException;
18
import java.net.InetSocketAddress;
19
import java.net.SocketAddress;
20
import java.nio.channels.SocketChannel;
21
import java.nio.file.Files;
22
import java.nio.file.Path;
23
import java.nio.file.Paths;
24
import java.util.*;
25
import java.util.function.Supplier;
26
import java.util.logging.Level;
27
import java.util.logging.Logger;
28
import java.util.regex.Matcher;
29
import java.util.regex.Pattern;
30

31
import static nats.io.NatsRunnerUtils.*;
32

33
/**
34
 * Server Runner
35
 */
36
public class NatsServerRunner implements AutoCloseable {
37
    public static final String ERROR_NOTE_PART_1 = "Make sure that the nats-server is installed and in your PATH.";
38
    public static final String ERROR_NOTE_PART_2 = "See https://github.com/nats-io/nats-server for information on installation";
39
    public static long DEFAULT_PROCESS_CHECK_WAIT = 100;
1✔
40
    public static int DEFAULT_PROCESS_CHECK_TRIES = 10;
1✔
41
    public static long DEFAULT_RUN_CHECK_WAIT = 100;
1✔
42
    public static int DEFAULT_RUN_CHECK_TRIES = 3;
1✔
43

44
    private final String _executablePath;
45
    private final Output _displayOut;
46
    private final Map<String, Integer> _ports;
47
    private final File _configFile;
48
    private final String _cmdLine;
49
    private Process process;
50

51
    /**
52
     * Get a new Builder
53
     * @return the builder
54
     */
55
    public static Builder builder() {
56
        return new Builder();
1✔
57
    }
58

59
    /**
60
     * Construct and start the Nats Server runner with all defaults:
61
     * <ul>
62
     * <li>use an automatically allocated port</li>
63
     * <li>no debug flag</li>
64
     * <li>jetstream not enabled</li>
65
     * <li>no custom config file</li>
66
     * <li>no config inserts</li>
67
     * <li>no custom args</li>
68
     * </ul>
69
     * @throws IOException thrown when the server cannot start
70
     */
71
    public NatsServerRunner() throws IOException {
72
        this(builder());
1✔
73
    }
1✔
74

75
    /**
76
     * Construct and start the Nats Server runner with defaults:
77
     * <ul>
78
     * <li>use an automatically allocated port</li>
79
     * <li>jetstream not enabled</li>
80
     * <li>no custom config file</li>
81
     * <li>no config inserts</li>
82
     * <li>no custom args</li>
83
     * </ul>
84
     * and this option:
85
     * @param debug whether to start the server with the -DV flags
86
     * @throws IOException thrown when the server cannot start
87
     */
88
    public NatsServerRunner(boolean debug) throws IOException {
89
        this(builder().debug(debug));
1✔
90
    }
1✔
91

92
    /**
93
     * Construct and start the Nats Server runner with defaults:
94
     * Consider using {@link Builder}
95
     * <ul>
96
     * <li>use an automatically allocated port</li>
97
     * <li>no custom config file</li>
98
     * <li>no config inserts</li>
99
     * <li>no custom args</li>
100
     * </ul>
101
     * and these options:
102
     * @param debug whether to start the server with the -DV flags
103
     * @param jetstream whether to enable JetStream
104
     * @throws IOException thrown when the server cannot start
105
     */
106
    public NatsServerRunner(boolean debug, boolean jetstream) throws IOException {
107
        this(builder().debug(debug).jetstream(jetstream));
1✔
108
    }
1✔
109

110
    /**
111
     * Construct and start the Nats Server runner with defaults:
112
     * <ul>
113
     * <li>jetstream not enabled</li>
114
     * <li>no custom config file</li>
115
     * <li>no config inserts</li>
116
     * <li>no custom args</li>
117
     * </ul>
118
     * and these options:
119
     * @param port the port to start on or &lt;=0 to use an automatically allocated port
120
     * @param debug whether to start the server with the -DV flags
121
     * @throws IOException thrown when the server cannot start
122
     */
123
    public NatsServerRunner(int port, boolean debug) throws IOException {
124
        this(builder().port(port).debug(debug));
1✔
125
    }
1✔
126

127
    /**
128
     * Construct and start the Nats Server runner with defaults:
129
     * <ul>
130
     * <li>no custom config file</li>
131
     * <li>no config inserts</li>
132
     * <li>no custom args</li>
133
     * </ul>
134
     * and these options:
135
     * @param port the port to start on or &lt;=0 to use an automatically allocated port
136
     * @param debug whether to start the server with the -DV flags
137
     * @param jetstream whether to enable JetStream
138
     * @throws IOException thrown when the server cannot start
139
     */
140
    public NatsServerRunner(int port, boolean debug, boolean jetstream) throws IOException {
141
        this(builder().port(port).debug(debug).jetstream(jetstream));
1✔
142
    }
1✔
143

144
    /**
145
     * Construct and start the Nats Server runner with defaults:
146
     * Consider using {@link Builder}
147
     * <ul>
148
     * <li>use an automatically allocated port</li>
149
     * <li>jetstream not enabled</li>
150
     * <li>no config inserts</li>
151
     * <li>no custom args</li>
152
     * </ul>
153
     * and these options:
154
     * @param debug whether to start the server with the -DV flags
155
     * @param configFilePath path to a custom config file
156
     * @throws IOException thrown when the server cannot start
157
     */
158
    public NatsServerRunner(String configFilePath, boolean debug) throws IOException {
159
        this(builder().debug(debug).configFilePath(configFilePath));
1✔
160
    }
1✔
161

162
    /**
163
     * Construct and start the Nats Server runner with defaults:
164
     * Consider using {@link Builder}
165
     * <ul>
166
     * <li>use an automatically allocated port</li>
167
     * <li>jetstream not enabled</li>
168
     * <li>no custom config file</li>
169
     * </ul>
170
     * and these options:
171
     * @param debug whether to start the server with the -DV flags
172
     * @param jetstream whether to enable JetStream
173
     * @param configFilePath path to a custom config file
174
     * @throws IOException thrown when the server cannot start
175
     */
176
    public NatsServerRunner(String configFilePath, boolean debug, boolean jetstream) throws IOException {
177
        this(builder().debug(debug).jetstream(jetstream).configFilePath(configFilePath));
1✔
178
    }
1✔
179

180
    /**
181
     * Construct and start the Nats Server runner with defaults:
182
     * Consider using {@link Builder}
183
     * <ul>
184
     * <li>jetstream not enabled</li>
185
     * <li>no custom args</li>
186
     * </ul>
187
     * and these options:
188
     * @param port the port to start on or &lt;=0 to use an automatically allocated port
189
     * @param debug whether to start the server with the -DV flags
190
     * @param configFilePath path to a custom config file
191
     * @param configInserts an array of custom lines to add to the config file
192
     * @throws IOException thrown when the server cannot start
193
     */
194
    public NatsServerRunner(String configFilePath, String[] configInserts, int port, boolean debug) throws IOException {
195
        this(builder().port(port).debug(debug).configFilePath(configFilePath).configInserts(configInserts));
1✔
196
    }
1✔
197

198
    /**
199
     * Construct and start the Nats Server runner with defaults:
200
     * Consider using {@link Builder}
201
     * <ul>
202
     * <li>jetstream not enabled</li>
203
     * <li>no config inserts</li>
204
     * <li>no custom args</li>
205
     * </ul>
206
     * and these options:
207
     * @param port the port to start on or &lt;=0 to use an automatically allocated port
208
     * @param debug whether to start the server with the -DV flags
209
     * @param configFilePath path to a custom config file
210
     * @throws IOException thrown when the server cannot start
211
     */
212
    public NatsServerRunner(String configFilePath, int port, boolean debug) throws IOException {
213
        this(builder().port(port).debug(debug).configFilePath(configFilePath));
1✔
214
    }
1✔
215

216
    /**
217
     * Construct and start the Nats Server runner with defaults:
218
     * Consider using {@link Builder}
219
     * <ul>
220
     * <li>use an automatically allocated port</li>
221
     * <li>no debug flag</li>
222
     * <li>jetstream not enabled</li>
223
     * <li>no custom config file</li>
224
     * <li>no config inserts</li>
225
     * </ul>
226
     * and these options:
227
     * @param customArgs any custom args to add to the command line
228
     * @throws IOException thrown when the server cannot start
229
     */
230
    public NatsServerRunner(String[] customArgs) throws IOException {
231
        this(builder().customArgs(customArgs));
1✔
232
    }
1✔
233

234
    /**
235
     * Construct and start the Nats Server runner with defaults:
236
     * Consider using {@link Builder}
237
     * <ul>
238
     * <li>use an automatically allocated port</li>
239
     * <li>jetstream not enabled</li>
240
     * <li>no custom config file</li>
241
     * <li>no config inserts</li>
242
     * </ul>
243
     * and these options:
244
     * @param debug whether to start the server with the -DV flags
245
     * @param customArgs any custom args to add to the command line
246
     * @throws IOException thrown when the server cannot start
247
     */
248
    public NatsServerRunner(String[] customArgs, boolean debug) throws IOException {
249
        this(builder().debug(debug).customArgs(customArgs));
1✔
250
    }
1✔
251

252
    /**
253
     * Construct and start the Nats Server runner with defaults:
254
     * Consider using {@link Builder}
255
     * <ul>
256
     * <li>use an automatically allocated port</li>
257
     * <li>no custom config file</li>
258
     * <li>no config inserts</li>
259
     * </ul>
260
     * and these options:
261
     * @param customArgs any custom args to add to the command line
262
     * @param debug whether to start the server with the -DV flags
263
     * @param jetstream whether to enable JetStream
264
     * @throws IOException thrown when the server cannot start
265
     */
266
    public NatsServerRunner(String[] customArgs, boolean debug, boolean jetstream) throws IOException {
267
        this(builder().debug(debug).jetstream(jetstream).customArgs(customArgs));
1✔
268
    }
1✔
269

270
    /**
271
     * Construct and start the Nats Server runner with defaults:
272
     * Consider using {@link Builder}
273
     * <ul>
274
     * <li>jetstream not enabled</li>
275
     * <li>no custom config file</li>
276
     * <li>no config inserts</li>
277
     * </ul>
278
     * and these options:
279
     * @param customArgs any custom args to add to the command line
280
     * @param port the port to start on or &lt;=0 to use an automatically allocated port
281
     * @param debug whether to start the server with the -DV flags
282
     * @throws IOException thrown when the server cannot start
283
     */
284
    public NatsServerRunner(String[] customArgs, int port, boolean debug) throws IOException {
285
        this(builder().port(port).debug(debug).customArgs(customArgs));
1✔
286
    }
1✔
287

288
    /**
289
     * Construct and start the Nats Server runner with options
290
     * Consider using {@link Builder}
291
     * @param port the port to start on or &lt;=0 to use an automatically allocated port
292
     * @param debug whether to start the server with the -DV flags
293
     * @param jetstream whether to enable JetStream
294
     * @param configFilePath path to a custom config file
295
     * @param configInserts an array of custom lines to add to the config file
296
     * @param customArgs any custom args to add to the command line
297
     * @throws IOException thrown when the server cannot start
298
     */
299
    public NatsServerRunner(int port, boolean debug, boolean jetstream, String configFilePath, String[] configInserts, String[] customArgs) throws IOException {
300
        this(builder().port(port).debug(debug).jetstream(jetstream).configFilePath(configFilePath).configInserts(configInserts).customArgs(customArgs));
1✔
301
    }
1✔
302

303
    public NatsServerRunner(NatsServerRunnerOptions natsServerRunnerOptions) throws Exception {
304
        this(builder().runnerOptions(natsServerRunnerOptions));
1✔
305
    }
1✔
306

307
    // ----------------------------------------------------------------------------------------------------
308
    // ACTUAL CONSTRUCTION
309
    // ----------------------------------------------------------------------------------------------------
310

311
    protected NatsServerRunner(Builder b) throws IOException {
1✔
312
        _executablePath = b.executablePath == null ? getResolvedServerPath() : b.executablePath.toString();
1✔
313
        _ports = b.ports;
1✔
314
        Integer tempPort = _ports.get(CONFIG_PORT_KEY);
1✔
315
        if (tempPort == null) {
1✔
316
            _ports.put(CONFIG_PORT_KEY, -1);
1✔
317
            tempPort = -1;
1✔
318
        }
319
        if (tempPort == -1) {
1✔
320
            tempPort = nextPort();
1✔
321
        }
322
        _ports.put(USER_PORT_KEY, tempPort);
1✔
323
        int userPort = tempPort;
1✔
324
        _ports.put(NATS_PORT_KEY, -1);
1✔
325
        _ports.put(NON_NATS_PORT_KEY, -1);
1✔
326

327
        if (b.output == null) {
1✔
328
            _displayOut = DefaultOutputSupplier.get();
1✔
329
            _displayOut.setLevel(DefaultOutputLevel);
1✔
330
        }
331
        else {
332
            _displayOut = b.output;
1✔
333
            if (b.outputLevel != null) {
1✔
334
                _displayOut.setLevel(b.outputLevel);
×
335
            }
336
        }
337

338
        long procCheckWait = b.processCheckWait == null ? DEFAULT_PROCESS_CHECK_WAIT : b.processCheckWait;
1✔
339
        int procCheckTries = b.processCheckTries == null ? DEFAULT_PROCESS_CHECK_TRIES : b.processCheckTries;
1✔
340
        long connCheckWait = b.connectCheckWait == null ? DEFAULT_RUN_CHECK_WAIT : b.connectCheckWait;
1✔
341
        int connCheckTries = b.connectCheckTries == null ? DEFAULT_RUN_CHECK_TRIES : b.connectCheckTries;
1✔
342

343
        List<String> cmd = new ArrayList<>();
1✔
344
        cmd.add(_executablePath);
1✔
345

346
        try {
347
            _configFile = File.createTempFile(CONF_FILE_PREFIX, CONF_FILE_EXT);
1✔
348
            BufferedWriter writer = new BufferedWriter(new FileWriter(_configFile));
1✔
349
            if (b.configFilePath == null) {
1✔
350
                _ports.put(NATS_PORT_KEY, userPort);
1✔
351
                writePortLine(writer, userPort);
1✔
352
            }
353
            else {
354
                processSuppliedConfigFile(writer, b.configFilePath);
1✔
355
            }
356

357
            if (b.configInserts != null) {
1✔
358
                for (String s : b.configInserts) {
1✔
359
                    writeLine(writer, s);
1✔
360
                }
1✔
361
            }
362

363
            writer.flush();
1✔
364
            writer.close();
1✔
365

366
            cmd.add(CONFIG_FILE_OPTION_NAME);
1✔
367
            cmd.add(_configFile.getAbsolutePath());
1✔
368
        }
369
        catch (IOException ioe) {
1✔
370
            _displayOut.error("%%% Error creating config file: " + ioe);
1✔
371
            throw ioe;
1✔
372
        }
1✔
373

374
        // Rewrite the port to a new one, so we don't reuse the same one over and over
375

376
        if (b.jetstream) {
1✔
377
            cmd.add(JETSTREAM_OPTION);
1✔
378
        }
379

380
        if (b.customArgs != null) {
1✔
381
            cmd.addAll(b.customArgs);
1✔
382
        }
383

384
        if (b.debugLevel != null) {
1✔
385
            cmd.add(b.debugLevel.getCmdOption());
1✔
386
        }
387

388
        _cmdLine = String.join(" ", cmd);
1✔
389

390
        NatsOutputLogger nol = null;
1✔
391
        try {
392
            ProcessBuilder pb = new ProcessBuilder(cmd);
1✔
393
            pb.redirectErrorStream(true);
1✔
394
            pb.redirectError(ProcessBuilder.Redirect.PIPE);
1✔
395
            pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
1✔
396
            _displayOut.info("%%% Starting [" + _cmdLine + "] with redirected IO");
1✔
397

398
            process = pb.start();
1✔
399
            nol = NatsOutputLogger.logOutput(_displayOut, process, DEFAULT_NATS_SERVER);
1✔
400

401
            int tries = procCheckTries;
1✔
402
            do {
403
                sleep(procCheckWait);
1✔
404
            }
405
            while (!process.isAlive() && --tries > 0);
1✔
406

407
            SocketAddress addr = new InetSocketAddress("localhost", _ports.get(NATS_PORT_KEY));
1✔
408
            SocketChannel socketChannel = SocketChannel.open();
1✔
409
            socketChannel.configureBlocking(true);
1✔
410
            if (connCheckTries > 0) {
1✔
411
                boolean checking = true;
1✔
412
                tries = connCheckTries;
1✔
413
                do {
414
                    try {
415
                        socketChannel.connect(addr);
1✔
416
                        checking = false;
1✔
417
                    }
418
                    catch (ConnectException e) {
1✔
419
                        if (--tries == 0) {
1✔
420
                            throw e;
×
421
                        }
422
                        sleep(connCheckWait);
1✔
423
                    } finally {
424
                        socketChannel.close();
1✔
425
                    }
426
                } while (checking);
1✔
427
            }
428

429
            _displayOut.info("%%% Started [" + _cmdLine + "]");
1✔
430
            nol.endStartupPhase();
1✔
431
        }
432
        catch (IOException ex) {
1✔
433
            _displayOut.error("%%% Failed to run [" + _cmdLine + "]");
1✔
434
            String exMsg = ex.getMessage();
1✔
435
            if (exMsg != null) {
1✔
436
                _displayOut.error("    " + ex.getMessage());
×
437
            }
438
            _displayOut.error("%%% " + ERROR_NOTE_PART_1);
1✔
439
            _displayOut.error("%%% " + ERROR_NOTE_PART_2);
1✔
440
            StringBuilder exMessage = new StringBuilder("Failed to run [").append(_cmdLine).append("]");
1✔
441
            if (b.fullErrorReportOnStartup) {
1✔
442
                if (nol != null) {
1✔
443
                    for (String line : nol.getStartupLines()) {
1✔
444
                        exMessage.append(System.lineSeparator()).append(line);
1✔
445
                    }
1✔
446
                }
447
                if (_cmdLine.contains(CONFIG_FILE_OPTION_NAME)) {
1✔
448
                    String configPath = _configFile.getAbsolutePath();
1✔
449
                    String configSep = getConfigSep(configPath);
1✔
450
                    exMessage.append(System.lineSeparator()).append(configSep);
1✔
451
                    exMessage.append(System.lineSeparator()).append(configPath);
1✔
452
                    exMessage.append(System.lineSeparator()).append(configSep);
1✔
453
                    try {
454
                        List<String> lines = Files.readAllLines(_configFile.toPath());
1✔
455
                        for (String line : lines) {
1✔
456
                            exMessage.append(System.lineSeparator()).append(line);
1✔
457
                        }
1✔
458
                    }
459
                    catch (Exception ignore) {
×
460
                    }
1✔
461
                    exMessage.append(System.lineSeparator()).append(configSep);
1✔
462
                }
463
            }
464
            throw new IllegalStateException(exMessage.toString());
1✔
465
        }
1✔
466
    }
1✔
467

468
    private String getConfigSep(String configPath) {
469
        StringBuilder sep = new StringBuilder("------------------------------");
1✔
470
        int len = configPath.length();
1✔
471
        while (sep.length() < len) {
1✔
472
            sep.append(sep);
1✔
473
        }
474
        return sep.substring(0, len);
1✔
475
    }
476

477
    // ----------------------------------------------------------------------------------------------------
478
    // HELPERS
479
    // ----------------------------------------------------------------------------------------------------
480
    private void processSuppliedConfigFile(BufferedWriter writer, Path configFilePath) throws IOException {
481
        Matcher constructionPortMatcher = Pattern.compile(PORT_REGEX).matcher("");
1✔
482
        Matcher mappedPortMatcher = Pattern.compile(PORT_MAPPED_REGEX).matcher("");
1✔
483

484
        BufferedReader reader = new BufferedReader(new FileReader(configFilePath.toFile()));
1✔
485

486
        boolean userTaken = false;
1✔
487
        int userPort = _ports.get(USER_PORT_KEY); // already ensured so it's not -1
1✔
488
        int natsPort = -1;
1✔
489
        String line = reader.readLine();
1✔
490
        int level = 0;
1✔
491
        while (line != null) {
1✔
492
            String trim = line.trim();
1✔
493
            if (trim.endsWith("{")) {
1✔
494
                level++;
1✔
495
            }
496
            else if (trim.startsWith("}")) {
1✔
497
                level--;
1✔
498
            }
499
            // replacing it here allows us to not care if the port is at the top level
500
            // or for instance inside a websocket block
501
            constructionPortMatcher.reset(line);
1✔
502
            if (constructionPortMatcher.find()) {
1✔
503
                if (userTaken) {
1✔
504
                    throw new IOException("Improper configuration, cannot assign port multiple times.");
1✔
505
                }
506
                userTaken = true;
1✔
507
                if (level == 0) {
1✔
508
                    natsPort = userPort;
1✔
509
                }
510
                else {
511
                    _ports.put(NON_NATS_PORT_KEY, userPort);
1✔
512
                }
513
                writeLine(writer, line.replace(constructionPortMatcher.group(1), Integer.toString(userPort)));
1✔
514
            }
515
            else {
516
                mappedPortMatcher.reset(line);
1✔
517
                if (mappedPortMatcher.find()) {
1✔
518
                    int start = line.indexOf("<");
1✔
519
                    int end = line.indexOf(">");
1✔
520
                    String key = line.substring(start + 1, end);
1✔
521
                    Integer mapped = _ports.get(key);
1✔
522
                    if (mapped == null) {
1✔
UNCOV
523
                        mapped = nextPort();
×
UNCOV
524
                        _ports.put(key, mapped);
×
525
                    }
526
                    writeLine(writer, line.replace("<" + key + ">", mapped.toString()));
1✔
527
                    if (level == 0) {
1✔
528
                        natsPort = mapped;
1✔
529
                    }
530
                    else {
531
                        _ports.put(NON_NATS_PORT_KEY, mapped);
1✔
532
                    }
533
                }
1✔
534
                else {
535
                    writeLine(writer, line);
1✔
536
                }
537
            }
538

539
            line = reader.readLine();
1✔
540
        }
1✔
541

542
        reader.close();
1✔
543

544
        if (natsPort == -1) {
1✔
545
            if (userTaken) {
1✔
546
                _ports.put(NATS_PORT_KEY, 4222);
1✔
547
            }
548
            else {
549
                _ports.put(NATS_PORT_KEY, userPort);
1✔
550
                writePortLine(writer, userPort);
1✔
551
            }
552
        }
553
        else {
554
            _ports.put(NATS_PORT_KEY, natsPort);
1✔
555
        }
556
    }
1✔
557

558
    private void writePortLine(BufferedWriter writer, int port) throws IOException {
559
        writeLine(writer, PORT_PROPERTY + port);
1✔
560
    }
1✔
561

562
    private void writeLine(BufferedWriter writer, String line) throws IOException {
563
        writer.write(line);
1✔
564
        writer.write("\n");
1✔
565
    }
1✔
566

567
    private void sleep(long sleep) {
568
        try {
569
            Thread.sleep(sleep);
1✔
570
        }
571
        catch (Exception ignore) {}
1✔
572
    }
1✔
573

574
    // ====================================================================================================
575
    // Getters
576
    // ====================================================================================================
577
    /**
578
     * The resolved server executable path being used
579
     * @return the path
580
     */
581
    public String getExecutablePath() {
582
        return _executablePath;
1✔
583
    }
584

585
    /**
586
     * Get the port number. Useful if it was automatically assigned
587
     * @return the port number
588
     */
589
    public int getPort() {
590
        return getUserPort();
1✔
591
    }
592

593
    public int getUserPort() {
594
        return _ports.get(USER_PORT_KEY);
1✔
595
    }
596

597
    public int getNatsPort() {
598
        return _ports.get(NATS_PORT_KEY);
1✔
599
    }
600

601
    public int getConfigPort() {
602
        return _ports.get(CONFIG_PORT_KEY);
1✔
603
    }
604

605
    public int getNonNatsPort() {
606
        return _ports.get(NON_NATS_PORT_KEY);
1✔
607
    }
608

609
    public Integer getPort(String key) {
610
        return _ports.get(key);
1✔
611
    }
612

613
    /**
614
     * Get the absolute path of the config file
615
     * @return the path
616
     */
617
    public String getConfigFile() {
618
        return _configFile.getAbsolutePath();
1✔
619
    }
620

621
    /**
622
     * Get the uri in the form nats://localhost:port
623
     * @return the uri string
624
     */
625
    public String getURI() {
626
        return getNatsLocalhostUri(getNatsPort());
1✔
627
    }
628

629
    /**
630
     * Get the command line used to start the server
631
     * @return the command line
632
     */
633
    public String getCmdLine() {
634
        return _cmdLine;
1✔
635
    }
636

637
    /**
638
     * Shut the server down
639
     * @param wait whether to block while waiting for the process to shut down
640
     * @throws InterruptedException if the wait was interrupted
641
     */
642
    public void shutdown(boolean wait) throws InterruptedException {
643
        if (process != null) {
1✔
644
            process.destroy();
1✔
645
            _displayOut.info("%%% Shut down [" + _cmdLine + "]");
1✔
646
            if (wait) {
1✔
647
                process.waitFor();
1✔
648
            }
649
            process = null;
1✔
650
        }
651
    }
1✔
652

653
    /**
654
     * Shut the server down, waiting (blocking)
655
     * @throws InterruptedException if the wait was interrupted
656
     */
657
    public void shutdown() throws InterruptedException {
658
        shutdown(true);
1✔
659
    }
1✔
660

661
    /**
662
     * For AutoCloseable, calls shutdown(true).
663
     */
664
    @Override
665
    public void close() throws Exception {
666
        shutdown(true);
1✔
667
    }
1✔
668

669
    // ====================================================================================================
670
    // Builder
671
    // ====================================================================================================
672
    public static class Builder {
1✔
673
        Map<String, Integer> ports = new HashMap<>();
1✔
674
        DebugLevel debugLevel;
675
        boolean jetstream;
676
        Path configFilePath;
677
        List<String> configInserts;
678
        List<String> customArgs;
679
        Path executablePath;
680
        Output output;
681
        Level outputLevel;
682
        Long processCheckWait;
683
        Integer processCheckTries;
684
        Long connectCheckWait;
685
        Integer connectCheckTries;
686
        boolean fullErrorReportOnStartup = true;
1✔
687

688
        public Builder port(Integer port) {
689
            return port(CONFIG_PORT_KEY, port);
1✔
690
        }
691

692
        public Builder port(String key, Integer port) {
693
            if (port == null) {
1✔
694
                ports.remove(key);
1✔
695
            }
696
            else {
697
                ports.put(key, port);
1✔
698
            }
699
            return this;
1✔
700
        }
701

702
        public Builder ports(Map<String, Integer> ports) {
703
            this.ports.clear();
1✔
704
            if (ports != null) {
1✔
705
                this.ports.putAll(ports);
1✔
706
            }
707
            return this;
1✔
708
        }
709

710
        public Builder debugLevel(DebugLevel debugLevel) {
711
            this.debugLevel = debugLevel;
1✔
712
            return this;
1✔
713
        }
714

715
        public Builder debug(boolean trueForDebugTraceFalseForNoDebug) {
716
            this.debugLevel = trueForDebugTraceFalseForNoDebug ? DebugLevel.DEBUG_TRACE : null;
1✔
717
            return this;
1✔
718
        }
719

720
        public Builder jetstream() {
721
            this.jetstream = true;
1✔
722
            return this;
1✔
723
        }
724

725
        public Builder jetstream(boolean jetStream) {
726
            this.jetstream = jetStream;
1✔
727
            return this;
1✔
728
        }
729

730
        public Builder configFilePath(String configFilePath) {
731
            this.configFilePath = configFilePath == null ? null : Paths.get(configFilePath);
1✔
732
            return this;
1✔
733
        }
734

735
        public Builder configFilePath(Path configFilePath) {
736
            this.configFilePath = configFilePath;
1✔
737
            return this;
1✔
738
        }
739

740
        public Builder configInserts(List<String> configInserts) {
741
            this.configInserts = configInserts == null || configInserts.isEmpty() ? null : configInserts;
1✔
742
            return this;
1✔
743
        }
744

745
        public Builder configInserts(String[] configInserts) {
746
            this.configInserts = configInserts == null || configInserts.length == 0 ? null : Arrays.asList(configInserts);
1✔
747
            return this;
1✔
748
        }
749

750
        public Builder customArgs(List<String> customArgs) {
751
            this.customArgs = customArgs == null || customArgs.isEmpty() ? null : customArgs;
1✔
752
            return this;
1✔
753
        }
754

755
        public Builder customArgs(String[] customArgs) {
756
            this.customArgs = customArgs == null || customArgs.length == 0 ? null : Arrays.asList(customArgs);
1✔
757
            return this;
1✔
758
        }
759

760
        public Builder executablePath(String executablePath) {
761
            this.executablePath = executablePath == null ? null : Paths.get(executablePath);
1✔
762
            return this;
1✔
763
        }
764

765
        public Builder executablePath(Path executablePath) {
766
            this.executablePath = executablePath;
1✔
767
            return this;
1✔
768
        }
769

770
        public Builder output(Output output) {
771
            this.output = output;
1✔
772
            return this;
1✔
773
        }
774

775
        public Builder outputLogger(Logger logger) {
776
            this.output = logger == null ? null : new LoggingOutput(logger);
1✔
777
            return this;
1✔
778
        }
779

780
        public Builder outputLevel(Level level) {
781
            this.outputLevel = level;
1✔
782
            return this;
1✔
783
        }
784

785
        public Builder processCheckWait(Long processWait) {
786
            this.processCheckWait = processWait;
1✔
787
            return this;
1✔
788
        }
789

790
        public Builder processCheckTries(Integer processCheckTries) {
791
            this.processCheckTries = processCheckTries;
1✔
792
            return this;
1✔
793
        }
794

795
        public Builder connectCheckWait(Long connectCheckWait) {
796
            this.connectCheckWait = connectCheckWait;
1✔
797
            return this;
1✔
798
        }
799

800
        public Builder connectCheckTries(Integer connectCheckTries) {
801
            this.connectCheckTries = connectCheckTries;
1✔
802
            return this;
1✔
803
        }
804

805
        public Builder fullErrorReportOnStartup(boolean fullErrorReportOnStartup) {
806
            this.fullErrorReportOnStartup = fullErrorReportOnStartup;
1✔
807
            return this;
1✔
808
        }
809

810
        public Builder runnerOptions(NatsServerRunnerOptions nsro) {
811
            port(nsro.port())
1✔
812
                .debugLevel(nsro.debugLevel())
1✔
813
                .jetstream(nsro.jetStream())
1✔
814
                .configFilePath(nsro.configFilePath())
1✔
815
                .configInserts(nsro.configInserts())
1✔
816
                .customArgs(nsro.customArgs())
1✔
817
                .executablePath(nsro.executablePath())
1✔
818
                .outputLogger(nsro.logger())
1✔
819
                .outputLevel(nsro.logLevel());
1✔
820
            return this;
1✔
821
        }
822

823
        public NatsServerRunner build() throws IOException {
824
            return new NatsServerRunner(this);
1✔
825
        }
826

827
        public NatsServerRunnerOptions buildOptions() {
828
            return new NatsServerRunnerOptionsImpl(this);
1✔
829
        }
830
    }
831

832
    // ====================================================================================================
833
    // Runner Wide Setting
834
    // ====================================================================================================
835
    static final Supplier<Output> DefaultLoggingSupplier = () -> new LoggingOutput(Logger.getLogger(NatsServerRunner.class.getName()));
1✔
836

837
    private static Supplier<Output> DefaultOutputSupplier = DefaultLoggingSupplier;
1✔
838
    private static Level DefaultOutputLevel = Level.INFO;
1✔
839
    private static String PreferredServerPath = null;
1✔
840

841
    public static Supplier<Output> getDefaultOutputSupplier() {
842
        return DefaultOutputSupplier;
1✔
843
    }
844

845
    public static void setDefaultOutputSupplier(Supplier<Output> outputSupplier) {
846
        DefaultOutputSupplier = outputSupplier == null ? DefaultLoggingSupplier : outputSupplier;
1✔
847
    }
1✔
848

849
    public static Level getDefaultOutputLevel() {
850
        return DefaultOutputLevel;
1✔
851
    }
852

853
    public static void setDefaultOutputLevel(Level defaultOutputLevel) {
854
        DefaultOutputLevel = defaultOutputLevel;
1✔
855
    }
1✔
856

857
    public static String getPreferredServerPath() {
858
        return PreferredServerPath;
1✔
859
    }
860

861
    public static void setPreferredServerPath(String preferredServerPath) {
862
        PreferredServerPath = preferredServerPath == null || preferredServerPath.length() == 0 ? null : preferredServerPath;
1✔
863
    }
1✔
864

865
    public static void clearPreferredServerPath() {
866
        PreferredServerPath = null;
1✔
867
    }
1✔
868
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc