* A PackageAssembler class to assemble a package content stream fragemented
* by PackageFragmenter back to a raw package data array.
*
- * @author Roland Haeder <webmaster@ship-simu.org>
+ * @author Roland Haeder <webmaster@shipsimu.org>
* @version 0.0.0
* @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team
* @license GNU GPL 3.0 or any newer version
- * @link http://www.ship-simu.org
+ * @link http://www.shipsimu.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-class PackageAssembler extends BaseHubSystem implements Assembler, Registerable {
+class PackageAssembler extends BaseHubSystem implements Assembler, Registerable, Visitable {
+ /**
+ * Name for stacker holding raw data of multiple messages
+ */
+ const STACKER_NAME_MULTIPLE_MESSAGE = 'multiple_message';
+
/**
* Pending data
*/
private $pendingData = '';
+ /**
+ * Private call-back methods
+ */
+ private $callbacks = array();
+
/**
* Protected constructor
*
// Set package instance here
$assemblerInstance->setPackageInstance($packageInstance);
+ // Create an instance of a raw data input stream
+ $streamInstance = ObjectFactory::createObjectByConfiguredName('node_raw_data_input_stream_class');
+
+ // And set it
+ $assemblerInstance->setInputStreamInstance($streamInstance);
+
+ // Now get a chunk handler instance
+ $handlerInstance = ChunkHandlerFactory::createChunkHandlerInstance();
+
+ // Set handler instance
+ $assemblerInstance->setHandlerInstance($handlerInstance);
+
+ // Get stacker instance
+ $stackerInstance = ObjectFactory::createObjectByConfiguredName('multiple_message_stacker_class');
+
+ // Initialize the only one stack
+ $stackerInstance->initStack(self::STACKER_NAME_MULTIPLE_MESSAGE);
+
+ // And add it
+ $assemblerInstance->setStackerInstance($stackerInstance);
+
// Return the prepared instance
return $assemblerInstance;
}
$isInputBufferEmpty = $this->getPackageInstance()->getStackerInstance()->isStackEmpty(NetworkPackage::STACKER_NAME_DECODED_HANDLED);
// Debug message
- //* NOISY-DEBUG: */ $this->debugOutput('PACKAGE-ASSEMBLER: isInputBufferEmpty=' . intval($isInputBufferEmpty));
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': isInputBufferEmpty=' . intval($isInputBufferEmpty));
// Return it
return $isInputBufferEmpty;
}
+ /**
+ * Checks whether given package content is completed (start/end markers are found)
+ *
+ * @param $packageContent An array with two elements: 'raw_data' and 'error_code'
+ * @return $isCompleted Whether the given package content is completed
+ */
+ private function isPackageContentCompleted (array $packageContent) {
+ // Check both
+ $isCompleted = $this->ifStartEndMarkersSet($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]);
+
+ // Return status
+ return $isCompleted;
+ }
+
/**
* Assembles the content from $packageContent. This method does only
* initialize the whole process by creating a call-back which will then
* chunks and (maybe) re-request some chunks from the sender, this would
* take to much time and therefore slow down this node again.
*
- * @param $packageContent An array with two elements: 'decoded_data' and 'error_code'
+ * @param $packageContent An array with two elements: 'raw_data' and 'error_code'
* @return void
* @throws UnsupportedPackageCodeHandlerException If the package code handler is not implemented
*/
public function chunkPackageContent (array $packageContent) {
// Validate the package content array again
assert(
- (isset($packageContent[BaseRawDataHandler::PACKAGE_DECODED_DATA])) &&
+ (isset($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA])) &&
(isset($packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]))
);
// Construct call-back name from package error code
- $methodName = 'handlePackageBy' . $this->convertToClassName($packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]);
+ $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]] = 'handlePackageBy' . $this->convertToClassName($packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]);
// Abort if the call-back method is not there
- if (!method_exists($this, $methodName)) {
+ if (!method_exists($this, $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]])) {
// Throw an exception
- throw new UnsupportedPackageCodeHandlerException(array($this, $methodName, $packageContent), BaseListener::EXCEPTION_UNSUPPORTED_PACKAGE_CODE_HANDLER);
+ throw new UnsupportedPackageCodeHandlerException(array($this, $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]], $packageContent), BaseListener::EXCEPTION_UNSUPPORTED_PACKAGE_CODE_HANDLER);
} // END - if
// Call it back
- call_user_func(array($this, $methodName), $packageContent);
+ call_user_func(array($this, $this->callbacks[$packageContent[BaseRawDataHandler::PACKAGE_ERROR_CODE]]), $packageContent);
}
/**************************************************************************
**************************************************************************/
/**
- * Call-back handler to handle unhandled packages. This method "explodes"
- * the string with the chunk separator from PackageFragmenter class, does
- * some low checks on it and feeds it into another queue for verification
- * and re-request for bad chunks.
+ * Call-back handler to handle unhandled package data. This method
+ * "explodes" the string with the chunk separator from PackageFragmenter
+ * class, does some low checks on it and feeds it into another queue for
+ * verification and re-request for bad chunks.
*
- * @param $packageContent An array with two elements: 'decoded_data' and 'error_code'
+ * @param $packageContent An array with two elements: 'raw_data' and 'error_code'
* @return void
- * @throws FinalChunkVerificationException If the final chunk does not start with 'EOP:'
*/
private function handlePackageByUnhandledPackage (array $packageContent) {
+ // Debug message
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ']: packageData[' . BaseRawDataHandler::PACKAGE_RAW_DATA . ']=' . $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]);
+
// Check for some conditions
- if (!$this->ifInputBufferIsEmpty()) {
+ if ((!$this->ifInputBufferIsEmpty()) || (!$this->isPackageContentCompleted($packageContent))) {
// Last chunk is not valid, so wait for more
- $this->pendingData .= $packageContent[BaseRawDataHandler::PACKAGE_DECODED_DATA];
+ $this->pendingData .= $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA];
// Debug message
- /* NOISY-DEBUG: */ $this->debugOutput('PACKAGE-ASSEMBLER: Partial data received. Waiting for more ... ( ' . strlen($packageContent[BaseRawDataHandler::PACKAGE_DECODED_DATA]) . ' bytes)');
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': Partial data received. Waiting for more ... ( ' . strlen($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]) . ' bytes)');
} else {
// Debug message
- //* NOISY-DEBUG */ $this->debugOutput('packageContent=' . print_r($packageContent,true) . ',chunks='.print_r($chunks,true));
-
- /*
- * "explode" the string from 'decoded_data' with chunk separator to
- * get an array of chunks. These chunks must then be verified by
- * their checksums. Also the final chunk must be handled.
- */
- $chunks = explode(PackageFragmenter::CHUNK_SEPARATOR, $packageContent[BaseRawDataHandler::PACKAGE_DECODED_DATA]);
-
- // Now get a chunk handler instance
- $handlerInstance = ChunkHandlerFactory::createChunkHandlerInstance();
-
- // Add all chunks because the last final chunk is found
- $handlerInstance->addAllChunksWithFinal($chunks);
+ /* DEBUG */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': packageContent=' . print_r($packageContent, TRUE) . ',chunks='.print_r($chunks, TRUE));
}
}
* @return void
*/
public function handlePendingData () {
+ // Debug output
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': Going to decode ' . strlen($this->pendingData) . ' Bytes of pending data. pendingData=' . $this->pendingData);
+
// Assert on condition
assert(!$this->isPendingDataEmpty());
+ // No markers set?
+ if (!$this->ifStartEndMarkersSet($this->pendingData)) {
+ // This will cause an assertition in next call, so simply wait for more data
+ /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': Pending data of ' . strlen($this->pendingData) . ' Bytes are incomplete, waiting for more ...');
+ return;
+ } elseif (substr_count($this->pendingData, BaseRawDataHandler::STREAM_START_MARKER) > 1) {
+ /*
+ * Multiple messages found, so split off first message as the input
+ * stream can only handle one message per time.
+ */
+ foreach (explode(BaseRawDataHandler::STREAM_START_MARKER, $this->pendingData) as $message) {
+ // Prepend start marker again as it is needed to decode the message.
+ $message = BaseRawDataHandler::STREAM_START_MARKER . $message;
+
+ // Push it on stack
+ $this->getStackerInstance()->pushNamed(self::STACKER_NAME_MULTIPLE_MESSAGE, $message);
+ } // END - foreach
+
+ // Clear pending data
+ $this->clearPendingData();
+
+ // ... and exit here
+ return;
+ }
+
// Init fake array
$packageContent = array(
- BaseRawDataHandler::PACKAGE_DECODED_DATA => $this->pendingData,
- BaseRawDataHandler::PACKAGE_ERROR_CODE => BaseRawDataHandler::SOCKET_ERROR_UNHANDLED
+ BaseRawDataHandler::PACKAGE_RAW_DATA => $this->getInputStreamInstance()->streamData($this->pendingData),
+ BaseRawDataHandler::PACKAGE_ERROR_CODE => BaseRawDataHandler::SOCKET_ERROR_UNHANDLED
);
// Clear pending data
- $this->pendingData = '';
+ $this->clearPendingData();
// Debug message
- /* NOISY-DEBUG: */ $this->debugOutput('PACKAGE-ASSEMBLER: Last block of partial data received. A total of ' . strlen($packageContent[BaseRawDataHandler::PACKAGE_DECODED_DATA]) . ' bytes has been received.');
+ //* 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.');
+
+ // Make sure last CHUNK_SEPARATOR is not there
+ if (substr($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA], -1, 1) == PackageFragmenter::CHUNK_SEPARATOR) {
+ // Remove it
+ $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA] = substr($packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA], 0, -1);
+ } // END - if
+
+ /*
+ * "explode" the string from 'raw_data' with chunk separator to
+ * get an array of chunks. These chunks must then be verified by
+ * their checksums. Also the final chunk must be handled.
+ */
+ $chunks = explode(PackageFragmenter::CHUNK_SEPARATOR, $packageContent[BaseRawDataHandler::PACKAGE_RAW_DATA]);
- // Call the real handler method
- $this->handlePackageByUnhandledPackage($packageContent);
+ // Add all chunks because the last final chunk is found
+ //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('PACKAGE-ASSEMBLER[' . __METHOD__ . ':' . __LINE__ . ': Going to add ' . count($chunks) . ' to chunk handler ...');
+ $this->getHandlerInstance()->addAllChunksWithFinal($chunks);
+ }
+
+ /**
+ * Accepts the visitor to process the visit "request"
+ *
+ * @param $visitorInstance An instance of a Visitor class
+ * @return void
+ */
+ public function accept (Visitor $visitorInstance) {
+ // Visit the assembler
+ $visitorInstance->visitAssembler($this);
+ }
+
+ /**
+ * Clears pending data
+ *
+ * @return void
+ */
+ public function clearPendingData () {
+ // Clear it
+ $this->pendingData = '';
}
}