Completed
Push — master ( 1e4163...17dff3 )
by mw
32:27
created

dispatchTempChangeOpPurgeJobWith()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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