]> git.mxchange.org Git - hub.git/blob - application/hub/main/nodes/class_BaseHubNode.php
64c84080a0586f4a69c57e9bfc9462ff5cbb0b4d
[hub.git] / application / hub / main / nodes / class_BaseHubNode.php
1 <?php
2 /**
3  * A general hub node class
4  *
5  * @author              Roland Haeder <webmaster@ship-simu.org>
6  * @version             0.0.0
7  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009, 2010 Hub Developer Team
8  * @license             GNU GPL 3.0 or any newer version
9  * @link                http://www.ship-simu.org
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 class BaseHubNode extends BaseHubSystem implements Updateable {
25         // Exception constants
26         const EXCEPTION_HUB_ALREADY_ANNOUNCED = 0xe00;
27
28         /**
29          * IP/port number of bootstrapping node
30          */
31         private $bootIpPort = '';
32
33         /**
34          * Query connector instance
35          */
36         private $connectorInstance = null;
37
38         /**
39          * Listener pool instance
40          */
41         private $listenerPoolInstance = null;
42
43         /**
44          * Wether this node is anncounced (KEEP ON false!)
45          * @deprecated
46          */
47         private $hubIsAnnounced = false;
48
49         /**
50          * State instance
51          */
52         private $stateInstance = null;
53
54         /**
55          * Wether this hub is active
56          */
57         private $isActive = false;
58
59         /**
60          * Protected constructor
61          *
62          * @param       $className      Name of the class
63          * @return      void
64          */
65         protected function __construct ($className) {
66                 // Call parent constructor
67                 parent::__construct($className);
68
69                 // Init state which sets the state to 'init'
70                 $this->initState();
71         }
72
73         /**
74          * Initializes the node's state which sets it to 'init'
75          *
76          * @return      void
77          */
78         private function initState() {
79                 // Get the state factory and create the initial state, we don't need
80                 // the state instance here
81                 StateFactory::createStateInstanceByName('init', $this);
82         }
83
84         /**
85          * Setter for node id
86          *
87          * @param       $nodeId         Our new node id
88          * @return      void
89          */
90         private final function setNodeId ($nodeId) {
91                 // Set it config now
92                 $this->getConfigInstance()->setConfigEntry('node_id', (string) $nodeId);
93         }
94
95         /**
96          * Getter for node id
97          *
98          * @return      $nodeId         Current node id
99          */
100         private final function getNodeId () {
101                 // Get it from config
102                 return $this->getConfigInstance()->getConfigEntry('node_id');
103         }
104
105         /**
106          * Setter for listener pool instance
107          *
108          * @param       $listenerPoolInstance   Our new listener pool instance
109          * @return      void
110          */
111         private final function setListenerPoolInstance (PoolableListener $listenerPoolInstance) {
112                 $this->listenerPoolInstance = $listenerPoolInstance;
113         }
114
115         /**
116          * Getter for listener pool instance
117          *
118          * @return      $listenerPoolInstance   Our current listener pool instance
119          */
120         public final function getListenerPoolInstance () {
121                 return $this->listenerPoolInstance;
122         }
123
124         /**
125          * Setter for state instance
126          *
127          * @param       $stateInstance  Node's current state instance
128          * @return      void
129          */
130         public final function setStateInstance (Stateable $stateInstance) {
131                 $this->stateInstance = $stateInstance;
132         }
133
134         /**
135          * Getter for state instance
136          *
137          * @return      $stateInstance  Node's current state instance
138          */
139         public final function getStateInstance () {
140                 return $this->stateInstance;
141         }
142
143         /**
144          * Setter for session id
145          *
146          * @param       $sessionId              Our new session id
147          * @return      void
148          */
149         private final function setSessionId ($sessionId) {
150                 $this->getConfigInstance()->setConfigEntry('session_id', (string) $sessionId);
151         }
152
153         /**
154          * Getter for session id
155          *
156          * @return      $sessionId              Current session id
157          */
158         public final function getSessionId () {
159                 return $this->getConfigInstance()->getConfigEntry('session_id');
160         }
161
162         /**
163          * Setter for query instance
164          *
165          * @param       $connectorInstance              Our new query instance
166          * @return      void
167          */
168         private final function setQueryConnectorInstance (Connectable $connectorInstance) {
169                 $this->connectorInstance = $connectorInstance;
170         }
171
172         /**
173          * Getter for query instance
174          *
175          * @return      $connectorInstance              Our new query instance
176          */
177         public final function getQueryConnectorInstance () {
178                 return $this->connectorInstance;
179         }
180
181         /**
182          * Getter for boot IP/port combination
183          *
184          * @return      $bootIpPort             The IP/port combination of the boot node
185          */
186         protected final function getBootIpPort () {
187                 return $this->bootIpPort;
188         }
189
190         /**
191          * "Getter" for a printable state name
192          */
193         public final function getPrintableState () {
194                 // Default is 'null'
195                 $stateName = 'null';
196
197                 // Get the state instance
198                 $stateInstance = $this->getStateInstance();
199
200                 // Is it an instance of Stateable?
201                 if ($stateInstance instanceof Stateable) {
202                         // Then use that state name
203                         $stateName = $stateInstance->getStateName();
204                 } // END - if
205
206                 // Return result
207                 return $stateName;
208         }
209
210         /**
211          * Checks wether the given IP address matches one of the bootstrapping nodes
212          *
213          * @param       $remoteAddr             IP address to checkout against our bootstrapping list
214          * @return      $isFound                Wether the IP is found
215          */
216         protected function ifAddressMatchesBootstrappingNodes ($remoteAddr) {
217                 // By default nothing is found
218                 $isFound = false;
219
220                 // Run through all configured IPs
221                 foreach (explode(',', $this->getConfigInstance()->getConfigEntry('hub_bootstrap_nodes')) as $ipPort) {
222                         // Split it up in IP/port
223                         $ipPortArray = explode(':', $ipPort);
224
225                         // Does it match?
226                         if ($ipPortArray[0] == $remoteAddr) {
227                                 // Found it!
228                                 $isFound = true;
229
230                                 // Remember the port number
231                                 $this->bootIpPort = $ipPort;
232
233                                 // Output message
234                                 $this->debugOutput('BOOTSTRAP: ' . __FUNCTION__ . '[' . __LINE__ . ']: IP matches remote address ' . $ipPort . '.');
235
236                                 // Stop further searching
237                                 break;
238                         } elseif ($ipPortArray[0] == $this->getConfigInstance()->getConfigEntry('node_listen_addr')) {
239                                 // IP matches listen address. At this point we really don't care
240                                 // if we can also listen on that address!
241                                 $isFound = true;
242
243                                 // Remember the port number
244                                 $this->bootIpPort = $ipPort;
245
246                                 // Output message
247                                 $this->debugOutput('BOOTSTRAP: ' . __FUNCTION__ . '[' . __LINE__ . ']: IP matches listen address ' . $ipPort . '.');
248
249                                 // Stop further searching
250                                 break;
251                         }
252                 } // END - foreach
253
254                 // Return the result
255                 return $isFound;
256         }
257
258         /**
259          * Outputs the console teaser. This should only be executed on startup or
260          * full restarts. This method generates some space around the teaser.
261          *
262          * @return      void
263          */
264         public function outputConsoleTeaser () {
265                 // Get the app instance (for shortening our code)
266                 $app = $this->getApplicationInstance();
267
268                 // Output all lines
269                 $this->debugOutput(' ');
270                 $this->debugOutput($app->getAppName() . ' v' . $app->getAppVersion() . ' - ' . $this->getRequestInstance()->getRequestElement('mode') . ' mode active');
271                 $this->debugOutput('Copyright (c) 2007 - 2008 Roland Haeder, 2009, 2010 Hub Developer Team');
272                 $this->debugOutput(' ');
273                 $this->debugOutput('This program comes with ABSOLUTELY NO WARRANTY; for details see docs/COPYING.');
274                 $this->debugOutput('This is free software, and you are welcome to redistribute it under certain');
275                 $this->debugOutput('conditions; see docs/COPYING for details.');
276                 $this->debugOutput(' ');
277         }
278
279         /**
280          * Generic method to acquire a hub-id. On first run this generates a new one
281          * based on many pseudo-random data. On any later run, unless the id
282          * got not removed from database, it will be restored from the database.
283          *
284          * @param       $requestInstance        A Requestable class
285          * @param       $responseInstance       A Responseable class
286          * @return      void
287          */
288         public function bootstrapAcquireHubId (Requestable $requestInstance, Responseable $responseInstance) {
289                 // Get a wrapper instance
290                 $wrapperInstance = ObjectFactory::createObjectByConfiguredName('node_info_db_wrapper_class');
291
292                 // Now get a search criteria instance
293                 $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class');
294
295                 // Search for the node number zero which is hard-coded the default
296                 $searchInstance->addCriteria(NodeInformationDatabaseWrapper::DB_COLUMN_NODE_NR, 1);
297                 $searchInstance->addCriteria(NodeInformationDatabaseWrapper::DB_COLUMN_NODE_TYPE, $this->getRequestInstance()->getRequestElement('mode'));
298                 $searchInstance->setLimit(1);
299
300                 // Get a result back
301                 $resultInstance = $wrapperInstance->doSelectByCriteria($searchInstance);
302
303                 // Is it valid?
304                 if ($resultInstance->next()) {
305                         // Save the result instance in this class
306                         $this->setResultInstance($resultInstance);
307
308                         // Get the node id from result and set it
309                         $this->setNodeId($this->getField(NodeInformationDatabaseWrapper::DB_COLUMN_NODE_ID));
310
311                         // Output message
312                         $this->debugOutput('BOOTSTRAP: Re-using found node-id: ' . $this->getNodeId() . '');
313                 } else {
314                         // Get an RNG instance (Random Number Generator)
315                         $rngInstance = ObjectFactory::createObjectByConfiguredName('rng_class');
316
317                         // Generate a pseudo-random string
318                         $randomString = $rngInstance->randomString(255) . ':' . $this->getBootIpPort()  . ':' . $this->getRequestInstance()->getRequestElement('mode');
319
320                         // Get a crypto instance
321                         $cryptoInstance = ObjectFactory::createObjectByConfiguredName('crypto_class');
322
323                         // Hash and encrypt the string so we become a "node id" aka Hub-Id
324                         $this->setNodeId($cryptoInstance->hashString($cryptoInstance->encryptString($randomString)));
325
326                         // Register the node id with our wrapper
327                         $wrapperInstance->registerNodeId($this, $this->getRequestInstance());
328
329                         // Output message
330                         $this->debugOutput('BOOTSTRAP: Created new node-id: ' . $this->getNodeId() . '');
331                 }
332         }
333
334         /**
335          * Generates a session id which will be sent to the other hubs and clients
336          *
337          * @return      void
338          */
339         public function bootstrapGenerateSessionId () {
340                 // Get an RNG instance
341                 $rngInstance = ObjectFactory::createObjectByConfiguredName('rng_class');
342
343                 // Generate a pseudo-random string
344                 $randomString = $rngInstance->randomString(255) . ':' . $this->getBootIpPort() . ':' . $this->getRequestInstance()->getRequestElement('mode');
345
346                 // Get a crypto instance
347                 $cryptoInstance = ObjectFactory::createObjectByConfiguredName('crypto_class');
348
349                 // Hash and encrypt the string so we become a "node id" aka Hub-Id
350                 $this->setSessionId($cryptoInstance->hashString($cryptoInstance->encryptString($randomString)));
351
352                 // Get a wrapper instance
353                 $wrapperInstance = ObjectFactory::createObjectByConfiguredName('node_info_db_wrapper_class');
354
355                 // Register the node id with our wrapper
356                 $wrapperInstance->registerSessionId($this, $this->getRequestInstance());
357
358                 // Output message
359                 $this->debugOutput('BOOTSTRAP: Created new session-id: ' . $this->getSessionId() . '');
360
361                 // Change the state because the node has auired a hub id
362                 $this->getStateInstance()->nodeGeneratedSessionId();
363         }
364
365         /**
366          * Initializes queues which every node needs
367          *
368          * @return      void
369          */
370         protected function initGenericQueues () {
371                 // Debug message
372                 $this->debugOutput('BOOTSTRAP: Initialize queues: START');
373
374                 // Set the query connector instance
375                 $this->setQueryConnectorInstance(ObjectFactory::createObjectByConfiguredName('query_connector_class', array($this)));
376
377                 // Run a test query
378                 $this->getQueryConnectorInstance()->doTestQuery();
379
380                 // Debug message
381                 $this->debugOutput('BOOTSTRAP: Initialize queues: FINISHED');
382         }
383
384         /**
385          * Adds hub data elements to a given dataset instance
386          *
387          * @param       $criteriaInstance       An instance of a storeable criteria
388          * @param       $requestInstance        An instance of a Requestable class
389          * @return      void
390          */
391         public function addElementsToDataSet (StoreableCriteria $criteriaInstance, Requestable $requestInstance) {
392                 // Add node number and type
393                 $criteriaInstance->addCriteria(NodeInformationDatabaseWrapper::DB_COLUMN_NODE_NR, 1);
394                 $criteriaInstance->addCriteria(NodeInformationDatabaseWrapper::DB_COLUMN_NODE_TYPE, $requestInstance->getRequestElement('mode'));
395
396                 // Add the node id
397                 $criteriaInstance->addCriteria(NodeInformationDatabaseWrapper::DB_COLUMN_NODE_ID, $this->getNodeId());
398
399                 // Add the session id if acquired
400                 if ($this->getSessionId() != '') {
401                         $criteriaInstance->addCriteria(NodeInformationDatabaseWrapper::DB_COLUMN_SESSION_ID, $this->getSessionId());
402                 } // END - if
403         }
404
405         /**
406          * Updates a given field with new value
407          *
408          * @param       $fieldName              Field to update
409          * @param       $fieldValue             New value to store
410          * @return      void
411          * @throws      DatabaseUpdateSupportException  If this class does not support database updates
412          * @todo        Try to make this method more generic so we can move it in BaseFrameworkSystem
413          */
414         public function updateDatabaseField ($fieldName, $fieldValue) {
415                 // Unfinished
416                 $this->partialStub('Unfinished!');
417                 return;
418
419                 // Get a critieria instance
420                 $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class');
421
422                 // Add search criteria
423                 $searchInstance->addCriteria(UserDatabaseWrapper::DB_COLUMN_USERNAME, $this->getUserName());
424                 $searchInstance->setLimit(1);
425
426                 // Now get another criteria
427                 $updateInstance = ObjectFactory::createObjectByConfiguredName('update_criteria_class');
428
429                 // Add criteria entry which we shall update
430                 $updateInstance->addCriteria($fieldName, $fieldValue);
431
432                 // Add the search criteria for searching for the right entry
433                 $updateInstance->setSearchInstance($searchInstance);
434
435                 // Set wrapper class name
436                 $updateInstance->setWrapperConfigEntry('user_db_wrapper_class');
437
438                 // Remember the update in database result
439                 $this->getResultInstance()->add2UpdateQueue($updateInstance);
440         }
441
442         /**
443          * Announces this hub to the upper (bootstrap or list) hubs. After this is
444          * successfully done the given task is unregistered from the handler.
445          *
446          * @param       $taskInstance   The task instance running this announcement
447          * @return      void
448          * @throws      HubAlreadyAnnouncedException    If this hub is already announced
449          * @todo        Change the first if() block to check for a specific state
450          */
451         public function announceSelfToUpperNodes (Taskable $taskInstance) {
452                 // Is this hub node announced?
453                 if ($this->hubIsAnnounced === true) {
454                         // Already announced!
455                         throw new HubAlreadyAnnouncedException($this, self::EXCEPTION_HUB_ALREADY_ANNOUNCED);
456                 } // END - if
457
458                 // Debug output
459                 $this->debugOutput('HUB: Self-announcement: START (taskInstance=' . $taskInstance->__toString(). ')');
460
461                 // Get a helper instance
462                 $helperInstance = ObjectFactory::createObjectByConfiguredName('hub_announcement_helper_class', array($this));
463
464                 // Load the announcement descriptor
465                 $helperInstance->loadAnnouncementDescriptor();
466
467                 // Set some dummy configuration entries, e.g. node_status
468                 $this->getConfigInstance()->setConfigEntry('node_status', $this->getStateInstance()->getStateName());
469
470                 // Compile all variables
471                 $helperInstance->getTemplateInstance()->compileConfigInVariables();
472
473                 // "Publish" the descriptor by sending it to the bootstrap/list nodes
474                 $helperInstance->publishAnnouncementDescriptor();
475
476                 // Change the state, this should be the last line except debug output
477                 $this->getStateInstance()->nodeAnnouncedToUpperHubs();
478
479                 // Debug output
480                 $this->debugOutput('HUB: Self-announcement: FINISHED');
481         }
482
483         /**
484          * Activates the hub by doing some final preparation and setting
485          * $hubIsActive to true
486          *
487          * @param       $requestInstance        A Requestable class
488          * @param       $responseInstance       A Responseable class
489          * @return      void
490          */
491         public function activateHub (Requestable $requestInstance, Responseable $responseInstance) {
492                 // Checks wether a listener is still active and shuts it down if one
493                 // is still listening.
494                 if (($this->determineIfListenerIsActive()) && ($this->getIsActive())) {
495                         // Shutdown them down before they can hurt anything
496                         $this->shutdownListenerPool();
497                 } // END - if
498
499                 // Get the controller here
500                 $controllerInstance = Registry::getRegistry()->getInstance('controller');
501
502                 // Run all filters for the hub activation
503                 $controllerInstance->executeActivationFilters($requestInstance, $responseInstance);
504
505                 // ----------------------- Last step from here ------------------------
506                 // Activate the hub. This is ALWAYS the last step in this method
507                 $this->getStateInstance()->nodeIsActivated();
508                 // ---------------------- Last step until here ------------------------
509         }
510
511         /**
512          * Initializes the listener pool (class)
513          *
514          * @return      void
515          */
516         public function initializeListenerPool () {
517                 // Debug output
518                 $this->debugOutput('HUB: Initialize listener: START');
519
520                 // Get a new pool instance
521                 $this->setListenerPoolInstance(ObjectFactory::createObjectByConfiguredName('listener_pool_class', array($this)));
522
523                 // Get an instance of the low-level listener
524                 $listenerInstance = ObjectFactory::createObjectByConfiguredName('tcp_listener_class', array($this));
525
526                 // Setup address and port
527                 $listenerInstance->setListenAddressByConfiguration('node_listen_addr');
528                 $listenerInstance->setListenPortByConfiguration('node_tcp_listen_port');
529
530                 // Initialize the listener
531                 $listenerInstance->initListener();
532
533                 // Get a decorator class
534                 $decoratorInstance = ObjectFactory::createObjectByConfiguredName('hub_tcp_listener_class', array($listenerInstance));
535
536                 // Add this listener to the pool
537                 $this->getListenerPoolInstance()->addListener($decoratorInstance);
538
539                 // Get a decorator class
540                 $decoratorInstance = ObjectFactory::createObjectByConfiguredName('client_tcp_listener_class', array($listenerInstance));
541
542                 // Add this listener to the pool
543                 $this->getListenerPoolInstance()->addListener($decoratorInstance);
544
545                 // Get an instance of the low-level listener
546                 $listenerInstance = ObjectFactory::createObjectByConfiguredName('udp_listener_class', array($this));
547
548                 // Setup address and port
549                 $listenerInstance->setListenAddressByConfiguration('node_listen_addr');
550                 $listenerInstance->setListenPortByConfiguration('node_udp_listen_port');
551
552                 // Initialize the listener
553                 $listenerInstance->initListener();
554
555                 // Get a decorator class
556                 $decoratorInstance = ObjectFactory::createObjectByConfiguredName('hub_udp_listener_class', array($listenerInstance));
557
558                 // Add this listener to the pool
559                 $this->getListenerPoolInstance()->addListener($decoratorInstance);
560
561                 // Get a decorator class
562                 $decoratorInstance = ObjectFactory::createObjectByConfiguredName('client_udp_listener_class', array($listenerInstance));
563
564                 // Add this listener to the pool
565                 $this->getListenerPoolInstance()->addListener($decoratorInstance);
566
567                 // Debug output
568                 $this->debugOutput('HUB: Initialize listener: FINISHED.');
569         }
570
571         /**
572          * Restores a previously stored node list from database
573          *
574          * @return      void
575          */
576         public function bootstrapRestoreNodeList () {
577                 // Debug output
578                 $this->debugOutput('HUB: Restore node list: START');
579
580                 // Get a wrapper instance
581                 $wrapperInstance = ObjectFactory::createObjectByConfiguredName('node_list_db_wrapper_class');
582
583                 // Now get a search criteria instance
584                 $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class');
585
586                 // Search for the node number zero which is hard-coded the default
587                 // @TODO Add some criteria, e.g. if the node is active or so
588                 //$searchInstance->addCriteria(NodeListDatabaseWrapper::DB_COLUMN_NODE_NR, 1);
589                 //$searchInstance->addCriteria(NodeListDatabaseWrapper::DB_COLUMN_NODE_TYPE, $this->getRequestInstance()->getRequestElement('mode'));
590                 //$searchInstance->setLimit(1);
591
592                 // Get a result back
593                 $resultInstance = $wrapperInstance->doSelectByCriteria($searchInstance);
594
595                 // Is it valid?
596                 if ($resultInstance->next()) {
597                         $this->partialStub('Do something for restoring the list.');
598                         // Output message
599                         //$this->debugOutput('HUB: ');
600                 } else {
601                         // No previously saved node list found!
602                         $this->debugOutput('HUB: No previously saved node list found. This is fine.');
603                 }
604
605                 // Debug output
606                 $this->debugOutput('HUB: Restore node list: FINISHED.');
607         }
608
609         /**
610          * Getter for isActive attribute
611          *
612          * @return      $isActive       Wether the hub is active
613          */
614         public final function getIsActive () {
615                 return $this->isActive;
616         }
617
618         /**
619          * Enables (default) or disables isActive flag
620          *
621          * @param       $isActive       Wether the hub is active
622          * @return      void
623          */
624         public final function enableIsActive ($isActive = true) {
625                 $this->isActive = (bool) $isActive;
626         }
627 }
628
629 // [EOF]
630 ?>