Completed
Push — master ( 4456fe...59e4d4 )
by Sébastien
03:55
created

StandaloneServer   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 79.52%

Importance

Changes 18
Bugs 1 Features 1
Metric Value
wmc 42
c 18
b 1
f 1
lcom 1
cbo 7
dl 0
loc 354
ccs 132
cts 166
cp 0.7952
rs 8.295

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 18 4
C start() 0 67 10
C stop() 0 53 10
A isStarted() 0 4 1
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 19
    public function __construct(StandaloneServer\Config $config, LoggerInterface $logger=null)
65
    {
66 19
        $this->config = $config;
67
68 19
        $curl_available = function_exists('curl_version');
69
70 19
        $this->portTester = new PortTester([
71 19
            '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 19
            'close_timeout_ms' => $curl_available ? null : 300
76 19
        ]);
77 19
        if ($logger === null) {
78 15
            $logger = new NullLogger();
79 15
        }
80 19
        $this->logger = $logger;
81 19
    }
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->notice("Starting standalone server on port $port.");
96
97 13
        if ($this->isStarted()) {
98 1
            $this->logger->notice("Standalone server already running, skipping start.");
99 1
            return;
100
        }
101
102 13
        if (!$this->portTester->isAvailable('localhost', $port, 'http')) {
103
            $msg = "Cannot start server on port '$port', it's already in use.";
104
            $this->logger->error("Start failed: $msg");
105
            throw new Exception\RuntimeException($msg);
106
        }
107
108 13
        $command = $this->getCommand();
109
110 13
        $log_file = $this->config->getLogFile();
111 13
        $pid_file = $this->config->getPidFile();
112 13
        $cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file);
113
114 13
        $this->logger->debug("Start server with: $cmd");
115 13
        exec($cmd);
116
117 13
        if (!file_exists($pid_file)) {
118
            $msg = "Server not started, pid file was not created in '$pid_file'";
119
            $this->logger->error("Start failed: $msg");
120
            throw new Exception\RuntimeException($msg);
121
        }
122 13
        if (!file_exists($log_file)) {
123
            $msg = "Server not started, log file was not created in '$log_file'";
124
            $this->logger->error("Start failed: $msg");
125
            throw new Exception\RuntimeException($msg);
126
        }
127
128
        // Loop for waiting correct start of phpjavabridge
129 13
        $started = false;
130 13
        $iterations = true;
131 13
        $refresh_us = 100 * 1000; // 100ms
132 13
        $timeout_us = $timeout_ms * 1000;
133 13
        $max_iterations = ceil($timeout_us / min([$refresh_us, $timeout_us]));
134
135 13
        while (!$started || $iterations > $max_iterations) {
136 13
            usleep($refresh_us);
137 13
            $log_file_content = file_get_contents($log_file);
138 13
            if (preg_match('/Exception/', $log_file_content)) {
139
                $msg = "Cannot start standalone server on port '$port', reason:\n";
140
                $msg .= $log_file_content;
141
                $this->logger->error("Start failed: $msg");
142
                throw new Exception\RuntimeException($msg);
143
            }
144
145 13
            $log_file_content = file_get_contents($log_file);
146 13
            if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) {
147 13
                $started = true;
148 13
            }
149 13
            $iterations++;
150 13
        }
151 13
        if (!$started) {
152
            $msg = "Standalone server probably not started, timeout '$timeout_ms' reached before getting output";
153
            $this->logger->error("Start failed: $msg");
154
            throw new Exception\RuntimeException($msg);
155
        }
156 13
        $this->started = true;
157 13
    }
158
159
160
161
    /**
162
     * Stop the standalone server
163
     *
164
     * @throws Exception\RuntimeException
165
     * @param boolean $throws_exception wether to throw exception if pid or process cannot be found or killed.
166
     * @return void
167
     */
168 16
    public function stop($throws_exception = false)
169
    {
170 16
        $this->logger->notice("Stopping server");
171 16
        $pid_file = $this->config->getPidFile();
172
        try {
173 16
            $pid = $this->getPid();
174 13
            $running = $this->isProcessRunning($throws_exception=true);
175 13
            if (!$running) {
176
                if ($throws_exception) {
177
                    $msg = "Cannot stop: pid exists ($pid) but server process is not running";
178
                    $this->logger->notice("Stop failed: $msg");
179
                    throw new Exception\RuntimeException($msg);
180
                }
181
                return;
182
            }
183 16
        } catch (Exception\PidNotFoundException $e) {
184 11
            if ($throws_exception) {
185 1
                $msg = "Cannot stop server, pid file not found (was the server started ?)";
186 1
                $this->logger->notice("Stop failed: $msg");
187 1
                throw new Exception\RuntimeException($msg);
188
            }
189 11
            return;
190
        }
191
192
193
        //$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...
194
        // Let sleep the process,
195
        // @todo: test sleep mith microseconds on different unix flavours
196 13
        $sleep_time = '0.2';
197 13
        $cmd = sprintf("kill %d; while ps -p %d; do sleep %s;done;", $pid, $pid, $sleep_time);
198
199 13
        exec($cmd, $output, $return_var);
200
        try {
201 13
            if ($return_var !== 0) {
202
                $msg = "Cannot kill standalone server process '$pid', seems to not exists.";
203
                $this->logger->notice("Stop failed: $msg");
204
                throw new Exception\RuntimeException($msg);
205
            }
206 13
        } catch (Exception\RuntimeException $e) {
207
            if ($throws_exception) {
208
                if (file_exists($pid_file)) {
209
                    unlink($pid_file);
210
                }
211
                throw $e;
212
            }
213
        }
214
215 13
        if (file_exists($pid_file)) {
216 13
            unlink($pid_file);
217 13
        }
218
219 13
        $this->started = false;
220 13
    }
221
222
    /**
223
     * Tells whether the standalone server is started
224
     * @return boolean
225
     */
226 13
    public function isStarted()
227
    {
228 13
        return $this->started;
229
    }
230
231
    /**
232
     * Return command used to start the standalone server
233
     * @return string
234
     */
235 13
    public function getCommand()
236
    {
237 13
        $port = $this->config->getPort();
238
239 13
        $java_bin = $this->config->getJavaBin();
240
241 13
        $jars = [];
242 13
        $classpaths = $this->config->getClasspaths();
243 13
        foreach ($classpaths as $classpath) {
244 11
            if (preg_match('/\*\.jar$/', $classpath)) {
245 11
                $directory = preg_replace('/\*\.jar$/', '', $classpath);
246 11
                $files = glob("$directory/*.jar");
247 11
                foreach ($files as $file) {
248
                    foreach ($files as $file) {
249
                        $jars[] = $file;
250
                    }
251 11
                }
252 11
            } else {
253
                $jars[] = $classpath;
254
            }
255 13
        }
256
257 13
        $jars[] = $this->config->getServerJar();
258 13
        $classpath = implode(':', $jars);
259
260 13
        $directives = ' -D' . implode(' -D', [
261 13
                    'php.java.bridge.daemon="false"',
262
                    'php.java.bridge.threads=50'
263 13
        ]);
264
265 13
        $command = sprintf('%s -cp "%s" %s php.java.bridge.Standalone SERVLET:%d',
266 13
                            $java_bin, $classpath, $directives, $port);
267
268 13
        return $command;
269
    }
270
271
272
    /**
273
     * Get standalone server pid number as it was stored during last start
274
     *
275
     * @throws Exception\PidNotFoundException
276
     * @return int
277
     */
278 18
    public function getPid()
279
    {
280 18
        $pid_file = $this->config->getPidFile();
281 18
        if (!file_exists($pid_file)) {
282 12
            $msg = "Pid file cannot be found '$pid_file'";
283 12
            $this->logger->info("Get PID failed: $msg");
284 12
            throw new Exception\PidNotFoundException($msg);
285
        }
286 14
        $pid = trim(file_get_contents($pid_file));
287 14
        if (!preg_match('/^[0-9]+$/', $pid)) {
288 1
            $msg = "Pid found '$pid_file' but no valid pid stored in it or corrupted file '$pid_file'.";
289 1
            $this->logger->error("Get PID failed: $msg");
290 1
            throw new Exception\PidCorruptedException($msg);
291
        }
292 14
        return (int) $pid;
293
    }
294
295
296
    /**
297
     * Return the content of the output_file
298
     * @throws Exception\RuntimeException
299
     * @return string
300
     */
301 5
    public function getOutput()
302
    {
303 5
        $log_file = $this->config->getLogFile();
304 5
        if (!file_exists($log_file)) {
305 1
            $msg = "Server output log file does not exists '$log_file'";
306 1
            $this->logger->error("Get server output failed: $msg");
307 1
            throw new Exception\RuntimeException($msg);
308 4
        } elseif (!is_readable($log_file)) {
309 1
            $msg = "Cannot read log file do to missing read permission '$log_file'";
310 1
            $this->logger->error("Get server output failed: $msg");
311 1
            throw new Exception\RuntimeException($msg);
312
        }
313 4
        $output = file_get_contents($log_file);
314 4
        return $output;
315
    }
316
317
318
    /**
319
     * Test whether the standalone server process
320
     * is effectively running
321
     *
322
     * @throws Exception\PidNotFoundException
323
     * @param boolean $throwsException if false discard exception if pidfile not exists
324
     * @return boolean
325
     */
326 14
    public function isProcessRunning($throwsException = false)
327
    {
328 14
        $running = false;
329
        try {
330 14
            $pid = $this->getPid();
331 14
            $cmd = sprintf("ps -j --no-headers -p %d", $pid);
332 14
            $this->logger->debug("Getting process with cmd: $cmd");
333 14
            $result = trim(shell_exec($cmd));
334 14
            if (preg_match("/^$pid/", $result)) {
335 14
                $running = true;
336 14
            }
337 14
        } catch (Exception\PidNotFoundException $e) {
338 1
            if ($throwsException) {
339
                throw $e;
340
            }
341
        }
342 14
        return $running;
343
    }
344
345
346
    /**
347
     * Restart the standalone server
348
     */
349 3
    public function restart()
350
    {
351 3
        $this->stop();
352 3
        $this->start();
353 3
    }
354
355
    /**
356
     * Return underlying configuration object
357
     * @return StandaloneServer\Config
358
     */
359 7
    public function getConfig()
360
    {
361 7
        return $this->config;
362
    }
363
}
364