Completed
Push — master ( 083ff9...d12817 )
by Sébastien
02:13
created

StandaloneServer   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 3

Test Coverage

Coverage 74.85%

Importance

Changes 11
Bugs 1 Features 1
Metric Value
wmc 53
c 11
b 1
f 1
lcom 3
cbo 3
dl 0
loc 395
ccs 128
cts 171
cp 0.7485
rs 7.4757

13 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 22 5
C start() 0 59 10
C stop() 0 37 8
A isStarted() 0 4 1
B getCommand() 0 24 2
A getPid() 0 14 3
A getOutput() 0 10 2
A restart() 0 5 1
A getServerPort() 0 4 1
A setServerPort() 0 4 1
A getConfig() 0 4 1
A getDefaultConfig() 0 11 2
D checkConfigRequiredArgs() 0 48 16

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
7
/*
8
  java -Djava.awt.headless="true"
9
  -Dphp.java.bridge.threads=50
10
  -Dphp.java.bridge.base=/usr/lib/php/modules
11
  -Dphp.java.bridge.php_exec=/usr/local/bin/php-cgi
12
  -Dphp.java.bridge.default_log_file=
13
  -Dphp.java.bridge.default_log_level=5
14
  -Dphp.java.bridge.daemon="false"
15
  -jar JavaBridge.jar
16
 * sudo netstat -anltp|grep :8089
17
 */
18
19
class StandaloneServer
20
{
21
22
    /**
23
     * @var int
24
     */
25
    protected $port;
26
27
    /**
28
     *
29
     * @var array
30
     */
31
    protected $config;
32
33
    /**
34
     * Tells whether the standalone server is started
35
     * @var boolean
36
     */
37
    protected $started = false;
38
39
    /**
40
     * @var array
41
     */
42
    protected $required_arguments = array(
43
        'port' => 'FILTER_VALIDATE_INT',
44
        'server_jar' => 'existing_file',
45
        'log_file' => 'file_with_existing_directory',
46
        'pid_file' => 'file_with_existing_directory',
47
        'autoload_path' => 'existing_directory'
48
    );
49
50
    /**
51
     * Default configuration options
52
     * @var array
53
     */
54
    protected $default_config = array(
55
        'server_jar' => '{base_dir}/resources/pjb621_standalone/JavaBridge.jar',
56
        'java_bin' => 'java',
57
        'log_file' => '{base_dir}/resources/pjb621_standalone/logs/pjbserver-port{tcp_port}.log',
58
        'pid_file' => '{base_dir}/resources/pjb621_standalone/var/run/pjbserver-port{tcp_port}.pid',
59
        'autoload_path' => '{base_dir}/resources/autoload'
60
    );
61
62
    /**
63
     *
64
     * @var PortTester
65
     */
66
    protected $portTester;
67
68
69
    /**
70
     * Constructor
71
     *
72
     * <code>
73
     *
74
     * $config = array(
75
     *      'port' => 8089,
76
     *
77
     *      // optionally
78
     *      'server_jar' => 'path/to/JavaBridge.jar'
79
     *      'java_bin' => 'path/to/java'
80
     * );
81
     * $server = new StandaloneServer($config);
82
     *
83
     * </code>
84
     *
85
     * @throws Exception\InvalidArgumentException
86
     * @param array $config
87
     */
88 11
    public function __construct(array $config)
89
    {
90 11
        if (!isset($config['port'])) {
91 3
            throw new Exception\InvalidArgumentException("Error missing required 'port' in config");
92 8
        } elseif (!filter_var($config['port'], FILTER_VALIDATE_INT)) {
93 1
            throw new Exception\InvalidArgumentException("Option 'port' must be numeric");
94
        }
95 7
        $config = array_merge($this->getDefaultConfig($config['port']), $config);
96 7
        $this->checkConfigRequiredArgs($config);
97 6
        $this->setServerPort($config['port']);
98 6
        $this->config = $config;
99
100 6
        $curl_available = function_exists('curl_version');
101
102 6
        $this->portTester = new PortTester(array(
103 6
            'backend' => $curl_available ? PortTester::BACKEND_CURL : PortTester::BACKEND_STREAM_SOCKET,
104
            // Close timout ms could be adjusted for your system
105
            // It prevent that port availability testing does
106
            // not close quickly enough to allow standalone server binding
107 6
            'close_timeout_ms' => $curl_available ? null : 300
108 6
        ));
109 6
    }
110
111
    /**
112
     * Start the standalone server
113
     *
114
     * @throws Exception\RuntimeException
115
     * @throws Exce
116
     *
117
     * @param int $timeout_ms maximum number of milliseconds to wait for server start
118
     * @return void
119
     */
120 4
    public function start($timeout_ms = 3000)
121
    {
122
123 4
        if ($this->isStarted()) {
124
            return;
125
        }
126
127 4
        $port = $this->getServerPort();
128
129 4
        if (!$this->portTester->isAvailable('localhost', $port, 'http')) {
130
            $msg = "Cannot start server on port '$port', it's already in use.";
131
            throw new Exception\RuntimeException($msg);
132
        }
133
134 4
        $command = $this->getCommand();
135
136 4
        $log_file = $this->config['log_file'];
137 4
        $pid_file = $this->config['pid_file'];
138 4
        $cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file);
139
140 4
        exec($cmd);
141
142 4
        if (!file_exists($pid_file)) {
143
            $msg = "Server not started, pid file was not created in '$pid_file'";
144
            throw new Exception\RuntimeException($msg);
145
        }
146 4
        if (!file_exists($log_file)) {
147
            $msg = "Server not started, log file was not created in '$log_file'";
148
            throw new Exception\RuntimeException($msg);
149
        }
150
151
        // Loop for waiting correct start of phpjavabridge
152 4
        $started = false;
153 4
        $iterations = true;
154 4
        $refresh_us = 100 * 1000; // 200ms
155 4
        $timeout_us = $timeout_ms * 1000;
156 4
        $max_iterations = ceil($timeout_us / min(array($refresh_us, $timeout_us)));
157
158 4
        while (!$started || $iterations > $max_iterations) {
159 4
            usleep($refresh_us);
160 4
            $log_file_content = file_get_contents($log_file);
161 4
            if (preg_match('/Exception/', $log_file_content)) {
162
                $msg = "Cannot start standalone server on port '$port', reason:\n";
163
                $msg .= $log_file_content;
164
                throw new Exception\RuntimeException($msg);
165
            }
166
167 4
            $log_file_content = file_get_contents($log_file);
168 4
            if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) {
169 4
                $started = true;
170 4
            }
171 4
            $iterations++;
172 4
        }
173 4
        if (!$started) {
174
            $msg = "Standalone server probably not started, timeout '$timeout_ms' reached before getting output";
175
            throw new Exception\RuntimeException($msg);
176
        }
177 4
        $this->started = true;
178 4
    }
179
180
181
182
    /**
183
     * Stop the standalone server
184
     *
185
     * @throws Exception\RuntimeException
186
     * @param boolean $throws_exception wether to throw exception if pid or process cannot be found or killed.
187
     * @return void
188
     */
189 4
    public function stop($throws_exception = false)
190
    {
191 4
        $pid_file = $this->config['pid_file'];
192
193
        try {
194 4
            $pid = $this->getPid();
195 4
        } catch (Exception\RuntimeException $e) {
196
            $msg = "Cannot stop server, pid cannot be determined (was the server started ?)";
197
            if ($throws_exception) {
198
                throw new Exception\RuntimeException($msg);
199
            }
200
        }
201
202 4
        $cmd = "kill $pid";
203
204 4
        exec($cmd, $output, $return_var);
205
206
        try {
207 4
            if ($return_var !== 0) {
208
                $msg = "Cannot kill standalone server process '$pid', seems to not exists.";
209
                throw new Exception\RuntimeException($msg);
210
            }
211 4
        } catch (Exception\RuntimeException $e) {
212
            if ($throws_exception) {
213
                if (file_exists($pid_file)) {
214
                    unlink($pid_file);
215
                }
216
                throw $e;
217
            }
218
        }
219
220 4
        if (file_exists($pid_file)) {
221 4
            unlink($pid_file);
222 4
        }
223
224 4
        $this->started = false;
225 4
    }
226
227
    /**
228
     * Tells whether the standalone server is started
229
     * @return boolean
230
     */
231 4
    public function isStarted()
232
    {
233 4
        return $this->started;
234
    }
235
236
    /**
237
     * Return command used to start the standalone server
238
     * @return string
239
     */
240 4
    public function getCommand()
241
    {
242 4
        $port = $this->getServerPort();
243
244 4
        $java_bin = $this->config['java_bin'];
245
246 4
        $jars = array();
247 4
        $autoload_path = $this->config['autoload_path'];
248 4
        $files = glob("$autoload_path/*.jar");
249 4
        foreach($files as $file) {
250 4
            $jars[] = $file;
251 4
        }
252 4
        $jars[] = $this->config['server_jar'];
253
        
254 4
        $classpath = join(':', $jars);
255
256 4
        $directives = ' -D' . join(' -D', array(
257 4
                    'php.java.bridge.daemon="false"',
258
                    'php.java.bridge.threads=30'
259 4
        ));
260
261 4
        $command = "$java_bin -cp $classpath $directives php.java.bridge.Standalone SERVLET:$port";
262 4
        return $command;
263
    }
264
265
266
    /**
267
     * Get runnin standalone server pid number
268
     *
269
     * @throws Exception\RuntimeException
270
     * @return int
271
     */
272 4
    public function getPid()
273
    {
274 4
        $pid_file = $this->config['pid_file'];
275 4
        if (!file_exists($pid_file)) {
276
            $msg = "Pid file cannot be found '$pid_file'";
277
            throw new Exception\RuntimeException($msg);
278
        }
279 4
        $pid = trim(file_get_contents($pid_file));
280 4
        if (!is_numeric($pid)) {
281
            $msg = "Pid found '$pid_file' but no valid pid stored in it.";
282
            throw new Exception\RuntimeException($msg);
283
        }
284 4
        return $pid;
285
    }
286
    
287
    
288
    /**
289
     * Return the content of the output_file
290
     * @throws \RuntimeException
291
     * @return string
292
     */
293 1
    public function getOutput()
294
    {
295 1
        $log_file = $this->config['log_file'];
296 1
        if (!file_exists($log_file)) {
297
            throw new \RuntimeException("Server output log file does not exists '$log_file'");
298
        }
299 1
        $output = file_get_contents($log_file);
300 1
        return $output;
301
        
302
    }
303
304
    /**
305
     * Restart the standalone server
306
     */
307
    public function restart()
308
    {
309
        $this->stop();
310
        $this->start();
311
    }
312
313
    /**
314
     * Return port on which standalone server listens
315
     * @return int
316
     */
317 5
    public function getServerPort()
318
    {
319 5
        return $this->port;
320
    }
321
322
    /**
323
     * Set port on which standalone server listens.
324
     *
325
     * @param int $port
326
     * @return void
327
     */
328 6
    protected function setServerPort($port)
329
    {
330 6
        $this->port = $port;
331 6
    }
332
333
    /**
334
     * Return standalone configuration
335
     *
336
     * @return array
337
     */
338 4
    public function getConfig()
339
    {
340 4
        return $this->config;
341
    }
342
343
    /**
344
     * Return default configuration options
345
     * @param int $port
346
     * @return array
347
     */
348 7
    protected function getDefaultConfig($port)
349
    {
350 7
        $base_dir = realpath(__DIR__ . '/../../../');
351 7
        $config = array();
352 7
        foreach ($this->default_config as $key => $value) {
353 7
            $tmp = str_replace('{base_dir}', $base_dir, $value);
354 7
            $tmp = str_replace('{tcp_port}', $port, $tmp);
355 7
            $config[$key] = $tmp;
356 7
        }
357 7
        return $config;
358
    }
359
360
    /**
361
     * Check configuration parameters
362
     * @throws Exception\InvalidArgumentException
363
     * @param array $config
364
     */
365 7
    protected function checkConfigRequiredArgs(array $config)
366
    {
367 7
        foreach ($this->required_arguments as $name => $type) {
368 7
            if (!isset($config[$name])) {
369
                $msg = "Missing option '$name' in Standalone server configuration";
370
                throw new Exception\InvalidArgumentException($msg);
371
            }
372 7
            if (is_long($type) && !filter_var($config[$name], $type)) {
373
                $msg = "Unsupported type in option '$name' in Standalone server configuration (required $type)";
374
                throw new Exception\InvalidArgumentException($msg);
375 7
            } elseif (is_string($type)) {
376
                switch ($type) {
377 7
                    case 'existing_file':
378 7
                        $file = $config[$name];
379 7
                        if (!file_exists($file)) {
380 1
                            $msg = "The '$name' file '$file 'does not exists.";
381 1
                            throw new Exception\InvalidArgumentException($msg);
382
                        }
383 6
                        break;
384 7
                    case 'existing_directory':
385 6
                        $directory = $config[$name];
386 6
                        if (!is_dir($directory)) {
387
                            $msg = "The '$name' directory '$directory 'does not exists.";
388
                            throw new Exception\InvalidArgumentException($msg);
389
                        }
390 6
                        break;
391
                        
392 7
                    case 'file_with_existing_directory':
393 6
                        $file = $config[$name];
394 6
                        $info = pathinfo($file);
395 6
                        $dirname = $info['dirname'];
396 6
                        if (!is_dir($dirname) || $dirname == ".") {
397
                            $msg = "Option '$name' refer to an invalid or non-existent directory ($file)";
398
                            throw new Exception\InvalidArgumentException($msg);
399
                        }
400 6
                        if (is_dir($file)) {
401
                            $msg = "Option '$name' does not refer to a file but an existing directory ($file)";
402
                            throw new Exception\InvalidArgumentException($msg);
403
                        }
404 6
                        if (file_exists($file) && !is_writable($file)) {
405
                            $msg = "File specified in '$name' is not writable ($file)";
406
                            throw new Exception\InvalidArgumentException($msg);
407
                        }
408 6
                        break;
409
                }
410 7
            }
411 7
        }
412 6
    }
413
}
414