Completed
Push — master ( 5d9cd9...874e6e )
by mw
62:18 queued 26:13
created

dispatchParserCachePurgeJobWith()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 2
dl 0
loc 8
ccs 0
cts 1
cp 0
crap 12
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SMW;
4
5
use Onoi\HttpRequest\HttpRequest;
6
use SMW\MediaWiki\Specials\SpecialDeferredRequestDispatcher;
7
use Psr\Log\LoggerInterface;
8
use Psr\Log\LoggerAwareInterface;
9
use Title;
10
11
/**
12
 * During the storage of a page, sometimes it is necessary to create extra
13
 * processing requests that should be executed asynchronously (due to large DB
14
 * processing time or is a secondary update) but without delay of the current
15
 * transaction. This class initiates and creates a separate request to be handled
16
 * by the receiving `SpecialDeferredRequestDispatcher` endpoint (if it can
17
 * connect).
18
 *
19
 * `DeferredRequestDispatchManager` allows to invoke jobs independent from the job
20
 * scheduler with the objective to be run timely to the current transaction
21
 * without having to wait on the job scheduler and without blocking the current
22
 * request.
23
 *
24
 * @license GNU GPL v2+
25
 * @since 2.3
26
 *
27
 * @author mwjames
28
 */
29
class DeferredRequestDispatchManager implements LoggerAwareInterface {
30
31
	/**
32
	 * @var HttpRequest
33
	 */
34
	private $httpRequest;
35
36
	/**
37
	 * Is kept static in order for the cli process to only make the check once
38
	 * and verify it can/cannot connect.
39
	 *
40
	 * @var boolean|null
41
	 */
42
	private static $canConnectToUrl = null;
43
44
	/**
45
	 * During unit tests, this parameter is set false to ensure that test execution
46
	 * does match expected results.
47
	 *
48
	 * @var boolean
49
	 */
50
	private $isEnabledHttpDeferredRequest = true;
51
52
	/**
53
	 * @var boolean
54
	 */
55
	private $isPreferredWithJobQueue = false;
56
57
	/**
58
	 * @var boolean
59
	 */
60
	private $isCommandLineMode = false;
61
62
	/**
63
	 * @var boolean
64
	 */
65
	private $isEnabledJobQueue = true;
66
67
	/**
68
	 * LoggerInterface
69 254
	 */
70 254
	private $logger;
71 254
72
	/**
73
	 * @since 2.3
74
	 *
75
	 * @param HttpRequest $httpRequest
76 8
	 */
77 8
	public function __construct( HttpRequest $httpRequest ) {
78 8
		$this->httpRequest = $httpRequest;
79 8
	}
80 8
81 8
	/**
82 8
	 * @since 2.3
83
	 */
84
	public function reset() {
85
		self::$canConnectToUrl = null;
86
		$this->isEnabledHttpDeferredRequest = true;
87
		$this->isPreferredWithJobQueue = false;
88
		$this->isCommandLineMode = false;
89 252
		$this->isEnabledJobQueue = true;
90 252
	}
91 252
92
	/**
93
	 * @see LoggerAwareInterface::setLogger
94
	 *
95
	 * @since 2.5
96
	 *
97
	 * @param LoggerInterface $logger
98
	 */
99
	public function setLogger( LoggerInterface $logger ) {
100
		$this->logger = $logger;
101
	}
102 245
103 245
	/**
104 245
	 * @since 2.3
105
	 *
106
	 * @param boolean $isEnabledHttpDeferredRequest
107
	 */
108
	public function isEnabledHttpDeferredRequest( $isEnabledHttpDeferredRequest ) {
109
		$this->isEnabledHttpDeferredRequest = (bool)$isEnabledHttpDeferredRequest;
110
	}
111
112
	/**
113
	 * Certain types of jobs or tasks may prefer to be executed using the job
114 199
	 * queue therefore indicate whether the dispatcher should try opening a
115 199
	 * http request or not.
116 199
	 *
117
	 * @since 2.5
118
	 *
119
	 * @param boolean $isPreferredWithJobQueue
120
	 */
121
	public function isPreferredWithJobQueue( $isPreferredWithJobQueue ) {
122
		$this->isPreferredWithJobQueue = (bool)$isPreferredWithJobQueue;
123 8
	}
124 8
125 8
	/**
126
	 * @see https://www.mediawiki.org/wiki/Manual:$wgCommandLineMode
127
	 * Indicates whether MW is running in command-line mode.
128
	 *
129
	 * @since 2.5
130
	 *
131
	 * @param boolean $isCommandLineMode
132
	 */
133 200
	public function isCommandLineMode( $isCommandLineMode ) {
134
		$this->isCommandLineMode = $isCommandLineMode;
135 200
	}
136
137
	/**
138
	 * @since 2.5
139 200
	 *
140
	 * @param boolean $isEnabledJobQueue
141
	 */
142
	public function isEnabledJobQueue( $isEnabledJobQueue ) {
143
		$this->isEnabledJobQueue = $isEnabledJobQueue;
144
	}
145
146
	/**
147
	 * @since 2.4
148 1
	 *
149 1
	 * @param Title $title
150
	 * @param array $parameters
151
	 */
152
	public function dispatchParserCachePurgeJobWith( Title $title, $parameters = array() ) {
153
154
		if ( $parameters === array() || !isset( $parameters['idlist'] ) ) {
155
			return;
156
		}
157
158
		return $this->dispatchJobRequestWith( 'SMW\ParserCachePurgeJob', $title, $parameters );
159
	}
160
161
	/**
162
	 * @since 2.5
163
	 *
164
	 * @param Title $title
165
	 * @param array $parameters
166
	 */
167
	public function dispatchFulltextSearchTableUpdateJobWith( Title $title, $parameters = array() ) {
168
		return $this->dispatchJobRequestWith( 'SMW\FulltextSearchTableUpdateJob', $title, $parameters );
169
	}
170
171
	/**
172
	 * @since 2.5
173
	 *
174 207
	 * @param Title $title
175
	 * @param array $parameters
176 207
	 */
177 1
	public function dispatchTempChangeOpPurgeJobWith( Title $title, $parameters = array() ) {
178
179
		if ( $parameters === array() || !isset( $parameters['slot:id'] ) || $parameters['slot:id'] === null ) {
180 206
			return;
181 206
		}
182 206
183
		return $this->dispatchJobRequestWith( 'SMW\TempChangeOpPurgeJob', $title, $parameters );
184
	}
185 206
186
	/**
187 206
	 * @since 2.3
188
	 *
189
	 * @param string $type
190 206
	 * @param Title $title
191 5
	 * @param array $parameters
192
	 */
193
	public function dispatchJobRequestWith( $type, Title $title, $parameters = array() ) {
194 201
195
		if ( !$this->isAllowedJobType( $type ) ) {
196 201
			return null;
197
		}
198
199 201
		$this->httpRequest->setOption(
200
			ONOI_HTTP_REQUEST_URL,
201
			SpecialDeferredRequestDispatcher::getTargetURL()
202 206
		);
203 206
204
		$dispatchableCallbackJob = $this->createDispatchableCallbackJob(
205
			$type,
206 206
			$this->isEnabledJobQueue
207
		);
208
209
		if ( $this->canUseDeferredRequest() ) {
210 201
			return $this->doPostJobWith( $type, $title, $parameters, $dispatchableCallbackJob );
211
		}
212
213
		call_user_func_array(
214
			$dispatchableCallbackJob,
215
			array( $title, $parameters )
216
		);
217 201
218
		return true;
219
	}
220
221 201
	private function canUseDeferredRequest() {
222 206
		return !$this->isCommandLineMode && !$this->isPreferredWithJobQueue && $this->isEnabledHttpDeferredRequest && $this->canConnectToUrl();
223
	}
224 206
225
	private function createDispatchableCallbackJob( $type, $isEnabledJobQueue ) {
226
227 207
		$callback = function ( $title, $parameters ) use ( $type, $isEnabledJobQueue ) {
228
229
			$job = ApplicationFactory::getInstance()->newJobFactory()->newByType(
230 207
				$type,
231
				$title,
232
				$parameters
233
			);
234
235
			// Only relevant when jobs are executed during a test (PHPUnit)
236 207
			$job->isEnabledJobQueue( $isEnabledJobQueue );
237
238
			// A `run` action would executed the job with the current transaction
239 5
			// defying the idea of a deferred process therefore only directly
240
			// have the job run when initiate from the commandLine as transactions
241 5
			// are expected without delay and are separated
242
			$this->isCommandLineMode ? $job->run() : $job->insert();
243
		};
244
245 5
		return $callback;
246
	}
247 5
248
	private function isAllowedJobType( $type ) {
249
250 5
		$allowedJobs = array(
251
			'SMW\ParserCachePurgeJob',
252
			'SMW\UpdateJob',
253 5
			'SMW\FulltextSearchTableUpdateJob',
254 5
			'SMW\TempChangeOpPurgeJob'
255
		);
256 5
257 5
		return in_array( $type, $allowedJobs );
258 5
	}
259
260
	private function canConnectToUrl() {
261 5
262 5
		if ( self::$canConnectToUrl !== null ) {
263 5
			return self::$canConnectToUrl;
264 5
		}
265
266
		$this->httpRequest->setOption( ONOI_HTTP_REQUEST_SSL_VERIFYPEER, false );
267
268
		return self::$canConnectToUrl = $this->httpRequest->ping();
269
	}
270 5
271
	private function doPostJobWith( $type, $title, $parameters, $dispatchableCallbackJob ) {
272 5
273
		// Build requestToken as source verification during the POST request
274
		$parameters['timestamp'] = time();
275
		$parameters['requestToken'] = SpecialDeferredRequestDispatcher::getRequestToken( $parameters['timestamp'] );
276
277
		$parameters['async-job'] = array(
278 5
			'type'  => $type,
279
			'title' => $title->getPrefixedDBkey()
280 5
		);
281
282 5
		$this->httpRequest->setOption( ONOI_HTTP_REQUEST_METHOD, 'POST' );
283
		$this->httpRequest->setOption( ONOI_HTTP_REQUEST_CONTENT_TYPE, "application/x-www-form-urlencoded" );
284
		$this->httpRequest->setOption( ONOI_HTTP_REQUEST_CONTENT, 'parameters=' . json_encode( $parameters ) );
285
		$this->httpRequest->setOption( ONOI_HTTP_REQUEST_CONNECTION_FAILURE_REPEAT, 2 );
286
287
		$this->httpRequest->setOption( ONOI_HTTP_REQUEST_ON_COMPLETED_CALLBACK, function( $requestResponse ) use ( $parameters ) {
288
			$requestResponse->set( 'type', $parameters['async-job']['type'] );
289
			$requestResponse->set( 'title', $parameters['async-job']['title'] );
290
			$this->log( 'SMW\DeferredRequestDispatchManager: ' . json_encode( $requestResponse->getList(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) );
291
		} );
292
293
		$this->httpRequest->setOption( ONOI_HTTP_REQUEST_ON_FAILED_CALLBACK, function( $requestResponse ) use ( $dispatchableCallbackJob, $title, $type, $parameters ) {
294
			$requestResponse->set( 'type', $parameters['async-job']['type'] );
295
			$requestResponse->set( 'title', $parameters['async-job']['title'] );
296
297
			$this->log( "SMW\DeferredRequestDispatchManager: Connection to SpecialDeferredRequestDispatcher failed, schedule a {$type}" );
298
			call_user_func_array( $dispatchableCallbackJob, array( $title, $parameters ) );
299
		} );
300
301
		$this->httpRequest->execute();
302
303
		return true;
304
	}
305
306
	private function log( $message, $context = array() ) {
307
308
		if ( $this->logger === null ) {
309
			return;
310
		}
311
312
		$this->logger->info( $message, $context );
313
	}
314
315
}
316