]> git.mxchange.org Git - hub.git/blob - application/hub/main/package/class_NetworkPackage.php
ddc95fe874eb8336a00ac4f3a535b2c74cdc6933
[hub.git] / application / hub / main / package / class_NetworkPackage.php
1 <?php
2 /**
3  * A NetworkPackage class. This class implements Deliverable because all network
4  * packages should be deliverable to other nodes. It further provides methods
5  * for reading raw content from template engines and feeding it to the stacker
6  * for undeclared packages.
7  *
8  * The factory method requires you to provide a compressor class (which must
9  * implement the Compressor interface). If you don't want any compression (not
10  * adviceable due to increased network load), please use the NullCompressor
11  * class and encode it with BASE64 for a more error-free transfer over the
12  * Internet.
13  *
14  * For performance reasons, this class should only be instanciated once and then
15  * used as a "pipe-through" class.
16  *
17  * @author              Roland Haeder <webmaster@ship-simu.org>
18  * @version             0.0.0
19  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2011 Hub Developer Team
20  * @license             GNU GPL 3.0 or any newer version
21  * @link                http://www.ship-simu.org
22  * @todo                Needs to add functionality for handling the object's type
23  *
24  * This program is free software: you can redistribute it and/or modify
25  * it under the terms of the GNU General Public License as published by
26  * the Free Software Foundation, either version 3 of the License, or
27  * (at your option) any later version.
28  *
29  * This program is distributed in the hope that it will be useful,
30  * but WITHOUT ANY WARRANTY; without even the implied warranty of
31  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32  * GNU General Public License for more details.
33  *
34  * You should have received a copy of the GNU General Public License
35  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
36  */
37 class NetworkPackage extends BaseFrameworkSystem implements Deliverable, Registerable {
38         /**
39          * Package mask for compressing package data:
40          * 0: Compressor extension
41          * 1: Raw package data
42          * 2: Tags, seperated by semicolons, no semicolon is required if only one tag is needed
43          * 3: Checksum
44          *                     0  1  2  3
45          */
46         const PACKAGE_MASK = '%s:%s:%s:%s';
47
48         /**
49          * Seperator for the above mask
50          */
51         const PACKAGE_MASK_SEPERATOR = ':';
52
53         /**
54          * Array indexes for above mask, start with zero
55          */
56         const INDEX_COMPRESSOR_EXTENSION = 0;
57         const INDEX_PACKAGE_DATA         = 1;
58         const INDEX_TAGS                 = 2;
59         const INDEX_CHECKSUM             = 3;
60
61         /**
62          * Array indexes for raw package array
63          */
64         const INDEX_PACKAGE_SENDER    = 0;
65         const INDEX_PACKAGE_RECIPIENT = 1;
66         const INDEX_PACKAGE_CONTENT   = 2;
67
68         /**
69          * Tags seperator
70          */
71         const PACKAGE_TAGS_SEPERATOR = ';';
72
73         /**
74          * Raw package data seperator
75          */
76         const PACKAGE_DATA_SEPERATOR = '|';
77
78         /**
79          * Stacker name for "undeclared" packages
80          */
81         const STACKER_NAME_UNDECLARED = 'undeclared';
82
83         /**
84          * Stacker name for "declared" packages (which are ready to send out)
85          */
86         const STACKER_NAME_DECLARED = 'declared';
87
88         /**
89          * Stacker name for "out-going" packages
90          */
91         const STACKER_NAME_OUTGOING = 'outgoing';
92
93         /**
94          * Stacker name for "back-buffered" packages
95          */
96         const STACKER_NAME_BACK_BUFFER = 'backbuffer';
97
98         /**
99          * Network target (alias): 'upper hubs'
100          */
101         const NETWORK_TARGET_UPPER_HUBS = 'upper';
102
103         /**
104          * Network target (alias): 'self'
105          */
106         const NETWORK_TARGET_SELF = 'self';
107
108         /**
109          * Protected constructor
110          *
111          * @return      void
112          */
113         protected function __construct () {
114                 // Call parent constructor
115                 parent::__construct(__CLASS__);
116
117                 // We need to initialize a stack here for our packages even those
118                 // which have no recipient address and stamp... ;-)
119                 $stackerInstance = ObjectFactory::createObjectByConfiguredName('network_package_stacker_class');
120
121                 // At last, set it in this class
122                 $this->setStackerInstance($stackerInstance);
123         }
124
125         /**
126          * Creates an instance of this class
127          *
128          * @param       $compressorInstance             A Compressor instance for compressing the content
129          * @return      $packageInstance                An instance of a Deliverable class
130          */
131         public static final function createNetworkPackage (Compressor $compressorInstance) {
132                 // Get new instance
133                 $packageInstance = new NetworkPackage();
134
135                 // Now set the compressor instance
136                 $packageInstance->setCompressorInstance($compressorInstance);
137
138                 // Return the prepared instance
139                 return $packageInstance;
140         }
141
142         /**
143          * "Getter" for hash from given content and helper instance
144          *
145          * @param       $content        Raw package content
146          * @param       $helperInstance         An instance of a BaseHubHelper class
147          * @param       $nodeInstance           An instance of a NodeHelper class
148          * @return      $hash   Hash for given package content
149          */
150         private function getHashFromContent ($content, BaseHubHelper $helperInstance, NodeHelper $nodeInstance) {
151                 // Create the hash
152                 // @TODO crc32 is not good, but it needs to be fast
153                 $hash = crc32(
154                         $content .
155                         ':' .
156                         $nodeInstance->getSessionId() .
157                         ':' .
158                         $this->getCompressorInstance()->getCompressorExtension()
159                 );
160
161                 // And return it
162                 return $hash;
163         }
164
165         /**
166          * Delivers the given raw package data.
167          *
168          * @param       $packageData    Raw package data in an array
169          * @return      void
170          */
171         private function declareRawPackageData (array $packageData) {
172                 /*
173                  * We need to disover every recipient, just in case we have a
174                  * multi-recipient entry like 'upper' is. 'all' may be a not so good
175                  * target because it causes an overload on the network and may be
176                  * abused for attacking the network with large packages.
177                  */
178                 $discoveryInstance = PackageDiscoveryFactory::createPackageDiscoveryInstance();
179
180                 // Discover all recipients, this may throw an exception
181                 $discoveryInstance->discoverRecipients($packageData);
182
183                 // Now get an iterator
184                 $iteratorInstance = $discoveryInstance->getIterator();
185
186                 // ... and begin iteration
187                 while ($iteratorInstance->valid()) {
188                         // Get current entry
189                         $currentRecipient = $iteratorInstance->current();
190
191                         // Debug message
192                         $this->debugOutput('PACKAGE: Package declared for recipient ' . $currentRecipient);
193
194                         // Set the recipient
195                         $packageData['recipient'] = $currentRecipient;
196
197                         // And enqueue it to the writer class
198                         $this->getStackerInstance()->pushNamed(self::STACKER_NAME_DECLARED, $packageData);
199
200                         // Skip to next entry
201                         $iteratorInstance->next();
202                 } // END - while
203
204                 // Clean-up the list
205                 $discoveryInstance->clearRecipients();
206         }
207
208         /**
209          * Delivers raw package data. In short, this will discover the raw socket
210          * resource through a discovery class (which will analyse the receipient of
211          * the package), register the socket with the connection (handler/helper?)
212          * instance and finally push the raw data on our outgoing queue.
213          *
214          * @param       $packageData    Raw package data in an array
215          * @return      void
216          */
217         private function deliverRawPackageData (array $packageData) {
218                 /*
219                  * This package may become big, depending on the shared object size or
220                  * delivered message size which shouldn't be so long (to save
221                  * bandwidth). Because of the nature of the used protocol (TCP) we need
222                  * to split it up into smaller pieces to fit it into a TCP frame.
223                  *
224                  * So first we need (again) a discovery class but now a protocol
225                  * discovery to choose the right socket resource. The discovery class
226                  * should take a look at the raw package data itself and then decide
227                  * which (configurable!) protocol should be used for that type of
228                  * package.
229                  */
230                 $discoveryInstance = SocketDiscoveryFactory::createSocketDiscoveryInstance();
231
232                 // Now discover the right protocol
233                 $socketResource = $discoveryInstance->discoverSocket($packageData);
234
235                 // We have to put this socket in our registry, so get an instance
236                 $registryInstance = SocketRegistry::createSocketRegistry();
237
238                 // Get the listener from registry
239                 $connectionInstance = Registry::getRegistry()->getInstance('connection');
240
241                 // Is it not there?
242                 if (!$registryInstance->isSocketRegistered($connectionInstance, $socketResource)) {
243                         // Then register it
244                         $registryInstance->registerSocket($connectionInstance, $socketResource, $packageData);
245                 } // END - if
246
247                 // We enqueue it again, but now in the out-going queue
248                 $this->getStackerInstance()->pushNamed(self::STACKER_NAME_OUTGOING, $packageData);
249         }
250
251         /**
252          * Sends waiting packages
253          *
254          * @param       $packageData    Raw package data
255          * @return      void
256          */
257         private function sendOutgoingRawPackageData (array $packageData) {
258                 // Get the right connection instance
259                 $connectionInstance = SocketRegistry::createSocketRegistry()->getHandlerInstanceFromPackageData($packageData);
260
261                 // Is this connection still alive?
262                 if ($connectionInstance->isShuttedDown()) {
263                         // This connection is shutting down
264                         // @TODO We may want to do somthing more here?
265                         return;
266                 } // END - if
267
268                 // Sent it away (we catch exceptions one method above
269                 $sentBytes = $connectionInstance->sendRawPackageData($packageData);
270
271                 // Remember unsent raw bytes in back-buffer, if any
272                 $this->storeUnsentBytesInBackBuffer($packageData, $sentBytes);
273         }
274
275         /**
276          * "Enqueues" raw content into this delivery class by reading the raw content
277          * from given template instance and pushing it on the 'undeclared' stack.
278          *
279          * @param       $helperInstance         An instance of a  BaseHubHelper class
280          * @param       $nodeInstance           An instance of a NodeHelper class
281          * @return      void
282          */
283         public function enqueueRawDataFromTemplate (BaseHubHelper $helperInstance, NodeHelper $nodeInstance) {
284                 // Get the raw content ...
285                 $content = $helperInstance->getTemplateInstance()->getRawTemplateData();
286
287                 // ... and compress it
288                 $content = $this->getCompressorInstance()->compressStream($content);
289
290                 // Add magic in front of it and hash behind it, including BASE64 encoding
291                 $content = sprintf(self::PACKAGE_MASK,
292                         // 1.) Compressor's extension
293                         $this->getCompressorInstance()->getCompressorExtension(),
294                         // 2.) Raw package content, encoded with BASE64
295                         base64_encode($content),
296                         // 3.) Tags
297                         implode(self::PACKAGE_TAGS_SEPERATOR, $helperInstance->getPackageTags()),
298                         // 4.) Checksum
299                         $this->getHashFromContent($content, $helperInstance, $nodeInstance)
300                 );
301
302                 // Now prepare the temporary array and push it on the 'undeclared' stack
303                 $this->getStackerInstance()->pushNamed(self::STACKER_NAME_UNDECLARED, array(
304                         'sender'    => $nodeInstance->getSessionId(),
305                         'recipient' => $helperInstance->getRecipientType(),
306                         'content'   => $content,
307                 ));
308         }
309
310         /**
311          * Checks wether a package has been enqueued for delivery.
312          *
313          * @return      $isEnqueued             Wether a package is enqueued
314          */
315         public function isPackageEnqueued () {
316                 // Check wether the stacker is not empty
317                 $isEnqueued = (($this->getStackerInstance()->isStackInitialized(self::STACKER_NAME_UNDECLARED)) && (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_UNDECLARED)));
318
319                 // Return the result
320                 return $isEnqueued;
321         }
322
323         /**
324          * Checks wether a package has been declared
325          *
326          * @return      $isDeclared             Wether a package is declared
327          */
328         public function isPackageDeclared () {
329                 // Check wether the stacker is not empty
330                 $isDeclared = (($this->getStackerInstance()->isStackInitialized(self::STACKER_NAME_DECLARED)) && (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_DECLARED)));
331
332                 // Return the result
333                 return $isDeclared;
334         }
335
336         /**
337          * Checks wether a package should be sent out
338          *
339          * @return      $isWaitingDelivery      Wether a package is waiting for delivery
340          */
341         public function isPackageWaitingForDelivery () {
342                 // Check wether the stacker is not empty
343                 $isWaitingDelivery = (($this->getStackerInstance()->isStackInitialized(self::STACKER_NAME_OUTGOING)) && (!$this->getStackerInstance()->isStackEmpty(self::STACKER_NAME_OUTGOING)));
344
345                 // Return the result
346                 return $isWaitingDelivery;
347         }
348
349         /**
350          * Delivers an enqueued package to the stated destination. If a non-session
351          * id is provided, recipient resolver is being asked (and instanced once).
352          * This allows that a single package is being delivered to multiple targets
353          * without enqueueing it for every target. If no target is provided or it
354          * can't be determined a NoTargetException is being thrown.
355          *
356          * @return      void
357          * @throws      NoTargetException       If no target can't be determined
358          */
359         public function declareEnqueuedPackage () {
360                 // Make sure this method isn't working if there is no package enqueued
361                 if (!$this->isPackageEnqueued()) {
362                         // This is not fatal but should be avoided
363                         // @TODO Add some logging here
364                         return;
365                 } // END - if
366
367                 // Now we know for sure there are packages to deliver, we can start
368                 // with the first one.
369                 $packageData = $this->getStackerInstance()->getNamed(self::STACKER_NAME_UNDECLARED);
370
371                 // Declare the raw package data for delivery
372                 $this->declareRawPackageData($packageData);
373
374                 // And remove it finally
375                 $this->getStackerInstance()->popNamed(self::STACKER_NAME_UNDECLARED);
376         }
377
378         /**
379          * Delivers the next declared package. Only one package per time will be sent
380          * because this may take time and slows down the whole delivery
381          * infrastructure.
382          *
383          * @return      void
384          */
385         public function deliverDeclaredPackage () {
386                 // Sanity check if we have packages declared
387                 if (!$this->isPackageDeclared()) {
388                         // This is not fatal but should be avoided
389                         // @TODO Add some logging here
390                         return;
391                 } // END - if
392
393                 // Get the package again
394                 $packageData = $this->getStackerInstance()->getNamed(self::STACKER_NAME_DECLARED);
395
396                 // And send it
397                 $this->deliverRawPackageData($packageData);
398
399                 // And remove it finally
400                 $this->getStackerInstance()->popNamed(self::STACKER_NAME_DECLARED);
401         }
402
403         /**
404          * Sends waiting packages out for delivery
405          *
406          * @return      void
407          */
408         public function sendWaitingPackage () {
409                 // Send any waiting bytes in the back-buffer before sending a new package
410                 $this->sendBackBufferBytes();
411
412                 // Sanity check if we have packages waiting for delivery
413                 if (!$this->isPackageWaitingForDelivery()) {
414                         // This is not fatal but should be avoided
415                         $this->debugOutput('PACKAGE: No package is waiting for delivery, but ' . __FUNCTION__ . ' was called.');
416                         return;
417                 } // END - if
418
419                 // Get the package again
420                 $packageData = $this->getStackerInstance()->getNamed(self::STACKER_NAME_OUTGOING);
421
422                 try {
423                         // Now try to send it
424                         $this->sendOutgoingRawPackageData($packageData);
425                         die("O!\n");
426
427                         // And remove it finally
428                         $this->getStackerInstance()->popNamed(self::STACKER_NAME_OUTGOING);
429                 } catch (InvalidSocketException $e) {
430                         // Output exception message
431                         $this->debugOutput('PACKAGE: Package was not delivered: ' . $e->getMessage());
432                 }
433         }
434 }
435
436 // [EOF]
437 ?>