Completed
Push — master ( 34fefe...4d65b3 )
by Sébastien
03:46
created

StandaloneServer   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 80.12%

Importance

Changes 17
Bugs 1 Features 1
Metric Value
wmc 42
c 17
b 1
f 1
lcom 1
cbo 7
dl 0
loc 356
ccs 133
cts 166
cp 0.8012
rs 8.295

10 Methods

Rating   Name   Duplication   Size   Complexity  
A isStarted() 0 4 1
A __construct() 0 18 4
C start() 0 69 10
C stop() 0 53 10
B getCommand() 0 35 5
A getPid() 0 16 3
A getOutput() 0 15 3
A isProcessRunning() 0 18 4
A restart() 0 5 1
A getConfig() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like StandaloneServer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StandaloneServer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PjbServer\Tools;
4
5
use PjbServer\Tools\Network\PortTester;
6
use Psr\Log\LoggerInterface;
7
use Psr\Log\NullLogger;
8
9
10
class StandaloneServer
11
{
12
13
    /**
14
     * @var int
15
     */
16
    protected $port;
17
18
    /**
19
     *
20
     * @var StandaloneServer\Config
21
     */
22
    protected $config;
23
24
    /**
25
     * Tells whether the standalone server is started
26
     * @var boolean
27
     */
28
    protected $started = false;
29
30
31
    /**
32
     *
33
     * @var PortTester
34
     */
35
    protected $portTester;
36
37
38
    /**
39
     * @var
40
     */
41
    protected $logger;
42
43
    /**
44
     * Constructor
45
     *
46
     * <code>
47
     *
48
     * $config = array(
49
     *      'port' => 8089,
50
     *
51
     *      // optionally
52
     *      'server_jar' => 'path/to/JavaBridge.jar'
53
     *      'java_bin' => 'path/to/java'
54
     * );
55
     * $server = new StandaloneServer($config);
56
     *
57
     * </code>
58
     *
59
     * @throws Exception\InvalidArgumentException
60
     * @param StandaloneServer\Config $config
61
     * @param LoggerInterface $logger
62
     *
63
     */
64 17
    public function __construct(StandaloneServer\Config $config, LoggerInterface $logger=null)
65
    {
66 17
        $this->config = $config;
67
68 17
        $curl_available = function_exists('curl_version');
69
70 17
        $this->portTester = new PortTester([
71 17
            'backend' => $curl_available ? PortTester::BACKEND_CURL : PortTester::BACKEND_STREAM_SOCKET,
72
            // Close timout ms could be adjusted for your system
73
            // It prevent that port availability testing does
74
            // not close quickly enough to allow standalone server binding
75 17
            'close_timeout_ms' => $curl_available ? null : 300
76 17
        ]);
77 17
        if ($logger === null) {
78 17
            $logger = new NullLogger();
79 17
        }
80 17
        $this->logger = $logger;
81 17
    }
82
83
    /**
84
     * Start the standalone server
85
     *
86
     * @throws Exception\RuntimeException
87
     *
88
     * @param int $timeout_ms maximum number of milliseconds to wait for server start
89
     * @return void
90
     */
91 13
    public function start($timeout_ms = 3000)
92
    {
93 13
        $port = $this->config->getPort();
94
95 13
        $this->logger->info("Starting standalone server on port $port.");
96
97 13
        if ($this->isStarted()) {
98 1
            $this->logger->info("Standalone server already running, skipping start.");
99 1
            return;
100
        }
101
102
103
104 13
        if (!$this->portTester->isAvailable('localhost', $port, 'http')) {
105
            $msg = "Cannot start server on port '$port', it's already in use.";
106 1
            $this->logger->error("Start failed: $msg");
107
            throw new Exception\RuntimeException($msg);
108
        }
109
110 13
        $command = $this->getCommand();
111
112 13
        $log_file = $this->config->getLogFile();
113 13
        $pid_file = $this->config->getPidFile();
114 13
        $cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file);
115
116 13
        $this->logger->debug("Start server with: $cmd");
117 13
        exec($cmd);
118
119 13
        if (!file_exists($pid_file)) {
120
            $msg = "Server not started, pid file was not created in '$pid_file'";
121
            $this->logger->error("Start failed: $msg");
122
            throw new Exception\RuntimeException($msg);
123
        }
124 13
        if (!file_exists($log_file)) {
125
            $msg = "Server not started, log file was not created in '$log_file'";
126
            $this->logger->error("Start failed: $msg");
127
            throw new Exception\RuntimeException($msg);
128
        }
129
130
        // Loop for waiting correct start of phpjavabridge
131 13
        $started = false;
132 13
        $iterations = true;
133 13
        $refresh_us = 100 * 1000; // 100ms
134 13
        $timeout_us = $timeout_ms * 1000;
135 13
        $max_iterations = ceil($timeout_us / min([$refresh_us, $timeout_us]));
136
137 13
        while (!$started || $iterations > $max_iterations) {
138 13
            usleep($refresh_us);
139 13
            $log_file_content = file_get_contents($log_file);
140 13
            if (preg_match('/Exception/', $log_file_content)) {
141
                $msg = "Cannot start standalone server on port '$port', reason:\n";
142
                $msg .= $log_file_content;
143
                $this->logger->error("Start failed: $msg");
144
                throw new Exception\RuntimeException($msg);
145
            }
146
147 13
            $log_file_content = file_get_contents($log_file);
148 13
            if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) {
149 13
                $started = true;
150 13
            }
151 13
            $iterations++;
152 13
        }
153 13
        if (!$started) {
154
            $msg = "Standalone server probably not started, timeout '$timeout_ms' reached before getting output";
155
            $this->logger->error("Start failed: $msg");
156
            throw new Exception\RuntimeException($msg);
157
        }
158 13
        $this->started = true;
159 13
    }
160
161
162
163
    /**
164
     * Stop the standalone server
165
     *
166
     * @throws Exception\RuntimeException
167
     * @param boolean $throws_exception wether to throw exception if pid or process cannot be found or killed.
168
     * @return void
169
     */
170 16
    public function stop($throws_exception = false)
171
    {
172 16
        $this->logger->info("Stopping server");
173 16
        $pid_file = $this->config->getPidFile();
174
        try {
175 16
            $pid = $this->getPid();
176 13
            $running = $this->isProcessRunning($throws_exception=true);
177 13
            if (!$running) {
178
                if ($throws_exception) {
179
                    $msg = "Cannot stop: pid exists ($pid) but server process is not running";
180
                    $this->logger->info("Stop failed: $msg");
181
                    throw new Exception\RuntimeException($msg);
182
                }
183
                return;
184
            }
185 16
        } catch (Exception\PidNotFoundException $e) {
186 11
            if ($throws_exception) {
187 1
                $msg = "Cannot stop server, pid file not found (was the server started ?)";
188 1
                $this->logger->info("Stop failed: $msg");
189 1
                throw new Exception\RuntimeException($msg);
190
            }
191 11
            return;
192
        }
193
194
195
        //$cmd = "kill $pid";
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
196
        // Let sleep the process,
197
        // @todo: test sleep mith microseconds on different unix flavours
198 13
        $sleep_time = '0.2';
199 13
        $cmd = sprintf("kill %d; while ps -p %d; do sleep %s;done;", $pid, $pid, $sleep_time);
200
201 13
        exec($cmd, $output, $return_var);
202
        try {
203 13
            if ($return_var !== 0) {
204
                $msg = "Cannot kill standalone server process '$pid', seems to not exists.";
205
                $this->logger->info("Stop failed: $msg");
206
                throw new Exception\RuntimeException($msg);
207
            }
208 13
        } catch (Exception\RuntimeException $e) {
209
            if ($throws_exception) {
210
                if (file_exists($pid_file)) {
211
                    unlink($pid_file);
212
                }
213
                throw $e;
214
            }
215
        }
216
217 13
        if (file_exists($pid_file)) {
218 13
            unlink($pid_file);
219 13
        }
220
221 13
        $this->started = false;
222 13
    }
223
224
    /**
225
     * Tells whether the standalone server is started
226
     * @return boolean
227
     */
228 13
    public function isStarted()
229
    {
230 13
        return $this->started;
231
    }
232
233
    /**
234
     * Return command used to start the standalone server
235
     * @return string
236
     */
237 13
    public function getCommand()
238
    {
239 13
        $port = $this->config->getPort();
240
241 13
        $java_bin = $this->config->getJavaBin();
242
243 13
        $jars = [];
244 13
        $classpaths = $this->config->getClasspaths();
245 13
        foreach ($classpaths as $classpath) {
246 11
            if (preg_match('/\*\.jar$/', $classpath)) {
247 11
                $directory = preg_replace('/\*\.jar$/', '', $classpath);
248 11
                $files = glob("$directory/*.jar");
249 11
                foreach ($files as $file) {
250
                    foreach ($files as $file) {
251
                        $jars[] = $file;
252
                    }
253 11
                }
254 11
            } else {
255
                $jars[] = $classpath;
256
            }
257 13
        }
258
259 13
        $jars[] = $this->config->getServerJar();
260 13
        $classpath = implode(':', $jars);
261
262 13
        $directives = ' -D' . implode(' -D', [
263 13
                    'php.java.bridge.daemon="false"',
264
                    'php.java.bridge.threads=50'
265 13
        ]);
266
267 13
        $command = sprintf('%s -cp "%s" %s php.java.bridge.Standalone SERVLET:%d',
268 13
                            $java_bin, $classpath, $directives, $port);
269
270 13
        return $command;
271
    }
272
273
274
    /**
275
     * Get standalone server pid number as it was stored during last start
276
     *
277
     * @throws Exception\PidNotFoundException
278
     * @return int
279
     */
280 16
    public function getPid()
281
    {
282 16
        $pid_file = $this->config->getPidFile();
283 16
        if (!file_exists($pid_file)) {
284 11
            $msg = "Pid file cannot be found '$pid_file'";
285 11
            $this->logger->error("Get PID failed: $msg");
286 11
            throw new Exception\PidNotFoundException($msg);
287
        }
288 13
        $pid = trim(file_get_contents($pid_file));
289 13
        if (!preg_match('/^[0-9]+$/', $pid)) {
290 1
            $msg = "Pid found '$pid_file' but no valid pid stored in it or corrupted file '$pid_file'.";
291 1
            $this->logger->error("Get PID failed: $msg");
292 1
            throw new Exception\PidCorruptedException($msg);
293
        }
294 13
        return (int) $pid;
295
    }
296
297
298
    /**
299
     * Return the content of the output_file
300
     * @throws Exception\RuntimeException
301
     * @return string
302
     */
303 3
    public function getOutput()
304
    {
305 3
        $log_file = $this->config->getLogFile();
306 3
        if (!file_exists($log_file)) {
307 1
            $msg = "Server output log file does not exists '$log_file'";
308 1
            $this->logger->error("Get server output failed: $msg");
309 1
            throw new Exception\RuntimeException($msg);
310 2
        } elseif (!is_readable($log_file)) {
311 1
            $msg = "Cannot read log file do to missing read permission '$log_file'";
312 1
            $this->logger->error("Get server output failed: $msg");
313 1
            throw new Exception\RuntimeException($msg);
314
        }
315 2
        $output = file_get_contents($log_file);
316 2
        return $output;
317
    }
318
319
320
    /**
321
     * Test whether the standalone server process
322
     * is effectively running
323
     *
324
     * @throws Exception\PidNotFoundException
325
     * @param boolean $throwsException if false discard exception if pidfile not exists
326
     * @return boolean
327
     */
328 13
    public function isProcessRunning($throwsException = false)
329
    {
330 13
        $running = false;
331
        try {
332 13
            $pid = $this->getPid();
333 13
            $cmd = sprintf("ps -j --no-headers -p %d", $pid);
334 13
            $this->logger->debug("Getting process with cmd: $cmd");
335 13
            $result = trim(shell_exec($cmd));
336 13
            if (preg_match("/^$pid/", $result)) {
337 13
                $running = true;
338 13
            }
339 13
        } catch (Exception\PidNotFoundException $e) {
340 1
            if ($throwsException) {
341
                throw $e;
342
            }
343
        }
344 13
        return $running;
345
    }
346
347
348
    /**
349
     * Restart the standalone server
350
     */
351 3
    public function restart()
352
    {
353 3
        $this->stop();
354 3
        $this->start();
355 3
    }
356
357
    /**
358
     * Return underlying configuration object
359
     * @return StandaloneServer\Config
360
     */
361 7
    public function getConfig()
362
    {
363 7
        return $this->config;
364
    }
365
}
366