]> git.mxchange.org Git - hub.git/blob - application/hub/main/package/assembler/class_PackageAssembler.php
Updated 'core'.
[hub.git] / application / hub / main / package / assembler / class_PackageAssembler.php
1 <?php
2 /**
3  * A PackageAssembler class to assemble a package content stream fragemented
4  * by PackageFragmenter back to a raw package data array.
5  *
6  * @author              Roland Haeder <webmaster@shipsimu.org>
7  * @version             0.0.0
8  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2015 Hub Developer Team
9  * @license             GNU GPL 3.0 or any newer version
10  * @link                http://www.shipsimu.org
11  *
12  * This program is free software: you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation, either version 3 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24  */
25 class PackageAssembler extends BaseHubSystem implements Assembler, Registerable, Visitable {
26         /**
27          * Name for stacker holding raw data of multiple messages
28          */
29         const STACKER_NAME_MULTIPLE_MESSAGE = 'multiple_message';
30
31         /**
32          * Pending data
33          */
34         private $pendingData = '';
35
36         /**
37          * Private call-back methods
38          */
39         private $callbacks = array();
40
41         /**
42          * Protected constructor
43          *
44          * @return      void
45          */
46         protected function __construct () {
47                 // Call parent constructor
48                 parent::__construct(__CLASS__);
49         }
50
51         /**
52          * Creates an instance of this class
53          *
54          * @param       $packageInstance        An instance of a Receivable class
55          * @return      $assemblerInstance      An instance of an Assembler class
56          */
57         public static final function createPackageAssembler (Receivable $packageInstance) {
58                 // Get new instance
59                 $assemblerInstance = new PackageAssembler();
60
61                 // Set package instance here
62                 $assemblerInstance->setPackageInstance($packageInstance);
63
64                 // Create an instance of a raw data input stream
65                 $streamInstance = ObjectFactory::createObjectByConfiguredName('node_raw_data_input_stream_class');
66
67                 // And set it
68                 $assemblerInstance->setInputStreamInstance($streamInstance);
69
70                 // Now get a chunk handler instance
71                 $handlerInstance = ChunkHandlerFactory::createChunkHandlerInstance();
72
73                 // Set handler instance
74                 $assemblerInstance->setHandlerInstance($handlerInstance);
75
76                 // Get stacker instance
77                 $stackInstance = ObjectFactory::createObjectByConfiguredName('multiple_message_stacker_class');
78
79                 // Initialize the only one stack
80                 $stackInstance->initStack(self::STACKER_NAME_MULTIPLE_MESSAGE);
81
82                 // And add it
83                 $assemblerInstance->setStackInstance($stackInstance);
84
85                 // Return the prepared instance
86                 return $assemblerInstance;
87         }
88
89         /**
90          * Checks whether the input buffer (stacker to be more preceise) is empty.
91          *
92          * @return      $isInputBufferEmpty             Whether the input buffer is empty
93          */
94         private function ifInputBufferIsEmpty () {
95                 // Check it
96                 $isInputBufferEmpty = $this->getPackageInstance()->getStackInstance()->isStackEmpty(NetworkPackage::STACKER_NAME_DECODED_HANDLED);
97
98                 // Debug message
99                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ']: isInputBufferEmpty=' . intval($isInputBufferEmpty));
100
101                 // Return it
102                 return $isInputBufferEmpty;
103         }
104
105         /**
106          * Checks whether given package content is completed (start/end markers are found)
107          *
108          * @param       $packageContent         An array with two elements: 'raw_data' and 'error_code'
109          * @return      $isCompleted            Whether the given package content is completed
110          */
111         private function isPackageContentCompleted (array $packageContent) {
112                 // Check both
113                 $isCompleted = $this->ifStartEndMarkersSet($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]);
114
115                 // Return status
116                 return $isCompleted;
117         }
118
119         /**
120          * Assembles the content from $packageContent. This method does only
121          * initialize the whole process by creating a call-back which will then
122          * itself (99.9% of all cases) "explode" the decoded data stream and add
123          * it to a chunk assembler queue.
124          *
125          * If the call-back method or this would attempt to assemble the package
126          * chunks and (maybe) re-request some chunks from the sender, this would
127          * take to much time and therefore slow down this node again.
128          *
129          * @param       $packageContent         An array with two elements: 'raw_data' and 'error_code'
130          * @return      void
131          * @throws      UnsupportedPackageCodeHandlerException  If the package code handler is not implemented
132          */
133         public function chunkPackageContent (array $packageContent) {
134                 // Validate the package content array again
135                 assert(
136                         (isset($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA])) &&
137                         (isset($packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]))
138                 );
139
140                 // Construct call-back name from package error code
141                 $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]] = 'handlePackageBy' . self::convertToClassName($packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]);
142
143                 // Abort if the call-back method is not there
144                 if (!method_exists($this, $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]])) {
145                         // Throw an exception
146                         throw new UnsupportedPackageCodeHandlerException(array($this, $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]], $packageContent), BaseListener::EXCEPTION_UNSUPPORTED_PACKAGE_CODE_HANDLER);
147                 } // END - if
148
149                 // Call it back
150                 call_user_func(array($this, $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]]), $packageContent);
151         }
152
153         /**************************************************************************
154          *                 Call-back methods for above method                     *
155          **************************************************************************/
156
157         /**
158          * Call-back handler to handle unhandled package data. This method
159          * "explodes" the string with the chunk separator from PackageFragmenter
160          * class, does some low checks on it and feeds it into another queue for
161          * verification and re-request for bad chunks.
162          *
163          * @param       $packageContent         An array with two elements: 'raw_data' and 'error_code'
164          * @return      void
165          */
166         private function handlePackageByUnhandledPackage (array $packageContent) {
167                 // Debug message
168                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ']: packageData[' . BaseRawDataHandler::PACKAGE_RAW_DATA . ']=' . $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]);
169
170                 // Check for some conditions
171                 if ((!$this->ifInputBufferIsEmpty()) || (!$this->isPackageContentCompleted($packageContent))) {
172                         // Last chunk is not valid, so wait for more
173                         $this->pendingData .= $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA];
174
175                         // Debug message
176                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ']: Partial data received. Waiting for more ... ( ' . strlen($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]) . ' bytes)');
177                 } else {
178                         // Debug message
179                         //* DEBUG */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': packageContent=' . print_r($packageContent, TRUE) . ',chunks='.print_r($chunks, TRUE));
180                 }
181         }
182
183         /**
184          * Checks whether the assembler's pending data is empty which means it has
185          * no pending data left for handling ... ;-)
186          *
187          * @return      $ifPendingDataIsEmpty   Whether pending data is empty
188          */
189         public function isPendingDataEmpty () {
190                 // A simbple check
191                 $ifPendingDataIsEmpty = empty($this->pendingData);
192
193                 // Return it
194                 return $ifPendingDataIsEmpty;
195         }
196
197         /**
198          * Checks whether the assembler has multiple messages pending
199          *
200          * @return      $isPending      Whether the assembler has multiple messages pending
201          */
202         public function ifMultipleMessagesPending () {
203                 // Determine it
204                 $isPending = (!$this->getStackInstance()->isStackEmpty(self::STACKER_NAME_MULTIPLE_MESSAGE));
205
206                 // Return it
207                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': isPending=' . intval($isPending));
208                 return $isPending;
209         }
210
211         /**
212          * Handles the assembler's pending data
213          *
214          * @return      void
215          */
216         public function handlePendingData () {
217                 // Debug output
218                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ']: Going to decode ' . strlen($this->pendingData) . ' Bytes of pending data. pendingData=' . $this->pendingData);
219
220                 // Assert on condition
221                 assert(!$this->isPendingDataEmpty());
222
223                 // No markers set?
224                 if (!$this->ifStartEndMarkersSet($this->pendingData)) {
225                         // This will cause an assertition in next call, so simply wait for more data
226                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ']: Pending data of ' . strlen($this->pendingData) . ' Bytes are incomplete, waiting for more ...');
227                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ']: this->pendingData=' . $this->pendingData);
228                         return;
229                 } elseif (substr_count($this->pendingData, BaseRawDataHandler::STREAM_START_MARKER) > 1) {
230                         /*
231                          * Multiple messages found, so split off first message as the input
232                          * stream can only handle one message per time.
233                          */
234                         foreach (explode(BaseRawDataHandler::STREAM_START_MARKER, $this->pendingData) as $message) {
235                                 // Prepend start marker again as it is needed to decode the message.
236                                 $message = BaseRawDataHandler::STREAM_START_MARKER . $message;
237
238                                 // Push it on stack
239                                 $this->getStackInstance()->pushNamed(self::STACKER_NAME_MULTIPLE_MESSAGE, $message);
240                         } // END - foreach
241
242                         // Clear pending data
243                         $this->clearPendingData();
244
245                         // ... and exit here
246                         return;
247                 }
248
249                 // Init fake array
250                 $packageContent = array(
251                         BaseRawDataHandler::PACKAGE_RAW_DATA   => $this->getInputStreamInstance()->streamData($this->pendingData),
252                         BaseRawDataHandler::PACKAGE_ERROR_CODE => BaseRawDataHandler::SOCKET_ERROR_UNHANDLED
253                 );
254
255                 /*
256                  * Clear pending data as it has been processed and will be handled some
257                  * lines below.
258                  */
259                 $this->clearPendingData();
260
261                 // Debug message
262                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ']: Last block of partial data received. A total of ' . strlen($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]) . ' bytes has been received.');
263
264                 // Make sure last CHUNK_SEPARATOR is not there
265                 if (substr($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA], -1, 1) == PackageFragmenter::CHUNK_SEPARATOR) {
266                         // Remove it
267                         $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA] = substr($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA], 0, -1);
268                 } // END - if
269
270                 /*
271                  * "explode" the string from 'raw_data' with chunk separator to get an
272                  * array of chunks. These chunks must then be verified by their
273                  * checksums. Also the final chunk must be handled.
274                  */
275                 $chunks = explode(PackageFragmenter::CHUNK_SEPARATOR, $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]);
276
277                 // Add all chunks because the last final chunk is found
278                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ']: Going to add ' . count($chunks) . ' to chunk handler ...');
279                 $this->getHandlerInstance()->addAllChunksWithFinal($chunks);
280         }
281
282         /**
283          * Handles multiple messages.
284          *
285          * @return      void
286          */
287         public function handleMultipleMessages () {
288                 // Assert on condition
289                 assert($this->ifMultipleMessagesPending());
290                 assert($this->isPendingDataEmpty());
291
292                 // "Pop" next entry from stack and set it as new pending data
293                 $this->pendingData = $this->getStackInstance()->popNamed(self::STACKER_NAME_MULTIPLE_MESSAGE);
294
295                 // And handle it
296                 $this->handlePendingData();
297         }
298
299         /**
300          * Accepts the visitor to process the visit "request"
301          *
302          * @param       $visitorInstance        An instance of a Visitor class
303          * @return      void
304          */
305         public function accept (Visitor $visitorInstance) {
306                 // Visit the assembler
307                 $visitorInstance->visitAssembler($this);
308         }
309
310         /**
311          * Clears pending data
312          *
313          * @return      void
314          */
315         public function clearPendingData () {
316                 // Clear it
317                 $this->pendingData = '';
318         }
319 }
320
321 // [EOF]
322 ?>