Completed
Push — master ( 53b63f...9d2d91 )
by Sébastien
02:44
created

StandaloneServer::stop()   C

Complexity

Conditions 8
Paths 13

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 13.2608

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 37
ccs 13
cts 23
cp 0.5652
rs 5.3846
cc 8
eloc 22
nc 13
nop 1
crap 13.2608
1
<?php
2
3
namespace PjbServer\Tools;
4
5
/*
6
java -Djava.awt.headless="true"
7
     -Dphp.java.bridge.threads=50
8
     -Dphp.java.bridge.base=/usr/lib/php/modules
9
     -Dphp.java.bridge.php_exec=/usr/local/bin/php-cgi
10
     -Dphp.java.bridge.default_log_file=
11
     -Dphp.java.bridge.default_log_level=5
12
     -Dphp.java.bridge.daemon="false"
13
     -jar JavaBridge.jar
14
*/
15
16
17
class StandaloneServer
18
{
19
20
    /**
21
     * @var int
22
     */
23
    protected $port;
24
25
    /**
26
     *
27
     * @var array
28
     */
29
    protected $config;
30
31
    /**
32
     * Tells whether the standalone server is started
33
     * @var boolean
34
     */
35
    protected $started = false;
36
37
    /**
38
     * @var array
39
     */
40
    protected $required_arguments = array(
41
        'port' => 'FILTER_VALIDATE_INT',
42
        'server_jar' => 'existing_file',
43
        'log_file' => 'file_with_existing_directory',
44
        'pid_file' => 'file_with_existing_directory',
45
    );
46
47
    /**
48
     * Default configuration options
49
     * @var array
50
     */
51
    protected $default_config = array(
52
        'server_jar' => '{base_dir}/resources/pjb621_standalone/JavaBridge.jar',
53
        'java_bin' => 'java',
54
        'log_file' => '{base_dir}/resources/pjb621_standalone/logs/pjbserver-port{tcp_port}.log',
55
        'pid_file' => '{base_dir}/resources/pjb621_standalone/var/run/pjbserver-port{tcp_port}.pid'
56
    );
57
58
    /**
59
     * Constructor
60
     *
61
     * <code>
62
     *
63
     * $config = array(
64
     *      'port' => 8089,
65
     *
66
     *      // optionally
67
     *      'server_jar' => 'path/to/JavaBridge.jar'
68
     *      'java_bin' => 'path/to/java'
69
     * );
70
     * $server = new StandaloneServer($config);
71
     *
72
     * </code>
73
     *
74
     * @throws Exception\InvalidArgumentException
75
     * @param array $config
76
     */
77 10
    public function __construct(array $config)
78
    {
79 10
        if (!isset($config['port'])) {
80 3
            throw new Exception\InvalidArgumentException("Error missing required 'port' in config");
81 7
        } elseif (!filter_var($config['port'], FILTER_VALIDATE_INT)) {
82 1
            throw new Exception\InvalidArgumentException("Option 'port' must be numeric");
83
        }
84 6
        $config = array_merge($this->getDefaultConfig($config['port']), $config);
85 6
        $this->checkConfigRequiredArgs($config);
86 5
        $this->setServerPort($config['port']);
87 5
        $this->config = $config;
88 5
    }
89
90
    /**
91
     * Start the standalone server
92
     *
93
     * @throws Exception\RuntimeException
94
     *
95
     * @return void
96
     */
97 3
    public function start()
98
    {
99
100 3
        if ($this->isStarted()) {
101
            return;
102
        }
103
104 3
        $port = $this->getServerPort();
105
106 3
        if (!$this->isPortAvailable('localhost', $port)) {
107 2
            $msg = "Cannot start server on port '$port', it's already in use.";
0 ignored issues
show
Unused Code introduced by
$msg is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
108
            //throw new Exception\RuntimeException($msg);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
109 2
        }
110
111 3
        $command = $this->getCommand();
112
113 3
        $log_file = $this->config['log_file'];
114 3
        $pid_file = $this->config['pid_file'];
115 3
        $cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file);
116
117 3
        exec($cmd);
118
119 3
        if (!file_exists($pid_file)) {
120
            $msg = "Server not started, pid file was not created in '$pid_file'";
121
            throw new Exception\RuntimeException($msg);
122
        }
123 3
        if (!file_exists($log_file)) {
124
            $msg = "Server not started, log file was not created in '$log_file'";
125
            throw new Exception\RuntimeException($msg);
126
        }
127
128
        // Loop for waiting correct start of phpjavabridge
129 3
        $started = false;
130 3
        $refresh_ms = 200; // 200ms
0 ignored issues
show
Unused Code introduced by
$refresh_ms is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
131 3
        while (!$started) {
132 3
            usleep(200 * 1000);
133 3
            $log_file_content = file_get_contents($log_file);
134 3
            if (preg_match('/Exception/', $log_file_content)) {
135
                $msg = "Cannot start standalone server on port '$port', reason:\n";
136
                $msg .= $log_file_content;
137
                throw new Exception\RuntimeException($msg);
138
            }
139
140 3
            clearstatcache($clear_realpath_cache = false, $log_file);
141 3
            $log_file_content = file_get_contents($log_file);
142 3
            if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) {
143 3
                $started = true;
144 3
            }
145
146 3
        }
147 3
        $this->started = true;
148 3
    }
149
150
    /**
151
     * Check if TCP port is available for binding
152
     *
153
     * @param int $port
154
     * @param int $timeout
155
     */
156 3
    protected function isPortAvailable($host, $port, $timeout = 1)
157
    {
158 3
        $available = false;
159 3
        $fp = @stream_socket_client("tcp://$host:$port", $errno, $errstr, $timeout);
160 3
        if (!$fp) {
161 1
            $available = true;
162 1
        } else {
163 2
            fclose($fp);
164
        }
165 3
        return $available;
166
    }
167
168
    /**
169
     * Return command used to start the standalone server
170
     * @return string
171
     */
172 3
    public function getCommand()
173
    {
174 3
        $port = $this->getServerPort();
175
176 3
        $java_bin = $this->config['java_bin'];
177
178 3
        $classpath = join(':', array(
179 3
            $this->config['server_jar'],
180 3
        ));
181
182 3
        $directives = ' -D' . join(' -D', array(
183 3
            'php.java.bridge.daemon="false"',
184
            'php.java.bridge.threads=50'
185 3
        ));
186
187 3
        $command = "$java_bin -cp $classpath $directives php.java.bridge.Standalone SERVLET:$port";
188 3
        return $command;
189
    }
190
191
    /**
192
     * Stop the standalone server
193
     *
194
     * @throws Exception\RuntimeException
195
     * @param boolean $throws_exception wether to throw exception if pid or process cannot be found or killed.
196
     * @return void
197
     */
198 3
    public function stop($throws_exception = false)
199
    {
200 3
        $pid_file = $this->config['pid_file'];
201
202
        try {
203 3
            $pid = $this->getPid();
204 3
        } catch (Exception\RuntimeException $e) {
205
            $msg = "Cannot stop server, pid cannot be determined (was the server started ?)";
0 ignored issues
show
Unused Code introduced by
$msg is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
206
            if ($throws_exception) {
207
                throw $e;
208
            }
209
        }
210
211 3
        $cmd = "kill $pid";
212
213 3
        exec($cmd, $output, $return_var);
214
215
        try {
216 3
            if ($return_var !== 0) {
217
                $msg = "Cannot kill standalone server process '$pid', seems to not exists.";
218
                throw new Exception\RuntimeException($msg);
219
            }
220 3
        } catch (Exception\RuntimeException $e) {
221
            if ($throws_exception) {
222
                if (file_exists($pid_file)) {
223
                    unlink($pid_file);
224
                }
225
                throw $e;
226
            }
227
        }
228
229 3
        if (file_exists($pid_file)) {
230 3
            unlink($pid_file);
231 3
        }
232
233 3
        $this->started = false;
234 3
    }
235
236
    /**
237
     * Tells whether the standalone server is started
238
     * @return boolean
239
     */
240 3
    public function isStarted()
241
    {
242 3
        return $this->started;
243
    }
244
245
    /**
246
     * Get runnin standalone server pid number
247
     *
248
     * @throws Exception\RuntimeException
249
     * @return int
250
     */
251 3
    public function getPid()
252
    {
253 3
        $pid_file = $this->config['pid_file'];
254 3
        if (!file_exists($pid_file)) {
255
            $msg = "Pid file cannot be found '$pid_file'";
256
            throw new Exception\RuntimeException($msg);
257
        }
258 3
        $pid = trim(file_get_contents($pid_file));
259 3
        if (!is_numeric($pid)) {
260
            $msg = "Pid found '$pid_file' but no valid pid stored in it.";
261
            throw new Exception\RuntimeException($msg);
262
        }
263 3
        return $pid;
264
    }
265
266
    /**
267
     * Restart the standalone server
268
     */
269
    public function restart()
270
    {
271
        $this->stop();
272
        $this->start();
273
    }
274
275
    /**
276
     * Return port on which standalone server listens
277
     * @return int
278
     */
279 4
    public function getServerPort()
280
    {
281 4
        return $this->port;
282
    }
283
284
    /**
285
     * Set port on which standalone server listens.
286
     *
287
     * @param int $port
288
     * @return void
289
     */
290 5
    protected function setServerPort($port)
291
    {
292 5
        $this->port = $port;
293 5
    }
294
295
    /**
296
     * Return standalone configuration
297
     *
298
     * @return array
299
     */
300 3
    public function getConfig()
301
    {
302 3
        return $this->config;
303
    }
304
305
    /**
306
     * Return default configuration options
307
     * @param int $port
308
     * @return array
309
     */
310 6
    protected function getDefaultConfig($port)
311
    {
312 6
        $base_dir = realpath(__DIR__ . '/../../../');
313 6
        $config = array();
314 6
        foreach ($this->default_config as $key => $value) {
315 6
            $tmp = str_replace('{base_dir}', $base_dir, $value);
316 6
            $tmp = str_replace('{tcp_port}', $port, $tmp);
317 6
            $config[$key] = $tmp;
318 6
        }
319 6
        return $config;
320
    }
321
322
    /**
323
     * Check configuration parameters
324
     * @throws Exception\InvalidArgumentException
325
     * @param array $config
326
     */
327 6
    protected function checkConfigRequiredArgs(array $config)
328
    {
329 6
        foreach ($this->required_arguments as $name => $type) {
330 6
            if (!isset($config[$name])) {
331
                $msg = "Missing option '$name' in Standalone server configuration";
332
                throw new Exception\InvalidArgumentException($msg);
333
            }
334 6
            if (is_long($type) && !filter_var($config[$name], $type)) {
335
                $msg = "Unsupported type in option '$name' in Standalone server configuration (required $type)";
336
                throw new Exception\InvalidArgumentException($msg);
337 6
            } elseif (is_string($type)) {
338
                switch ($type) {
339 6
                    case 'existing_file':
340 6
                        $file = $config[$name];
341 6
                        if (!file_exists($file)) {
342 1
                            $msg = "The '$name' file '$file 'does not exists.";
343 1
                            throw new Exception\InvalidArgumentException($msg);
344
                        }
345 5
                        break;
346 6
                    case 'file_with_existing_directory':
347 5
                        $file = $config[$name];
348 5
                        $info = pathinfo($file);
349 5
                        $dirname = $info['dirname'];
350 5
                        if (!is_dir($dirname) || $dirname == ".") {
351
                            $msg = "Option '$name' refer to an invalid or non-existent directory ($file)";
352
                            throw new Exception\InvalidArgumentException($msg);
353
                        }
354 5
                        if (is_dir($file)) {
355
                            $msg = "Option '$name' does not refer to a file but an existing directory ($file)";
356
                            throw new Exception\InvalidArgumentException($msg);
357
                        }
358 5
                        if (file_exists($file) && !is_writable($file)) {
359
                            $msg = "File specified in '$name' is not writable ($file)";
360
                            throw new Exception\InvalidArgumentException($msg);
361
                        }
362 5
                        break;
363
                }
364 6
            }
365 6
        }
366 5
    }
367
}
368