3 * A database wrapper for distributed hash tables
5 * @author Roland Haeder <webmaster@shipsimu.org>
7 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team
8 * @license GNU GPL 3.0 or any newer version
9 * @link http://www.shipsimu.org
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.
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.
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/>.
24 class NodeDistributedHashTableDatabaseWrapper extends BaseDatabaseWrapper implements NodeDhtWrapper, Registerable {
25 // Constants for database table names
26 const DB_TABLE_NODE_DHT = 'node_dht';
28 // Constants for database column names
29 const DB_COLUMN_NODE_ID = 'node_id';
30 const DB_COLUMN_SESSION_ID = 'session_id';
31 const DB_COLUMN_EXTERNAL_IP = 'external_ip';
32 const DB_COLUMN_LISTEN_PORT = 'listen_port';
33 const DB_COLUMN_PRIVATE_KEY_HASH = 'private_key_hash';
34 const DB_COLUMN_NODE_MODE = 'node_mode';
35 const DB_COLUMN_ACCEPTED_OBJECTS = 'accepted_object_types';
36 const DB_COLUMN_NODE_LIST = 'node_list';
39 const EXCEPTION_NODE_ALREADY_REGISTERED = 0x800;
40 const EXCEPTION_NODE_NOT_REGISTERED = 0x801;
43 * Protected constructor
47 protected function __construct () {
48 // Call parent constructor
49 parent::__construct(__CLASS__);
53 * Creates an instance of this database wrapper by a provided user class
55 * @return $wrapperInstance An instance of the created wrapper class
57 public static final function createNodeDistributedHashTableDatabaseWrapper () {
59 $wrapperInstance = new NodeDistributedHashTableDatabaseWrapper();
61 // Set (primary!) table name
62 $wrapperInstance->setTableName(self::DB_TABLE_NODE_DHT);
64 // Return the instance
65 return $wrapperInstance;
69 * Static getter for an array of all DHT database entries
71 * @return $elements All elements for the DHT dabase
73 public static final function getAllElements () {
74 // Create array and ...
76 self::DB_COLUMN_NODE_ID,
77 self::DB_COLUMN_SESSION_ID,
78 self::DB_COLUMN_EXTERNAL_IP,
79 self::DB_COLUMN_LISTEN_PORT,
80 self::DB_COLUMN_PRIVATE_KEY_HASH,
81 self::DB_COLUMN_NODE_MODE,
82 self::DB_COLUMN_ACCEPTED_OBJECTS,
83 self::DB_COLUMN_NODE_LIST
91 * Prepares a search instance for given node data
93 * @param $nodeData An array with valid node data
94 * @return $searchInstance An instance of a SearchCriteria class
96 private function prepareSearchInstance (array $nodeData) {
97 // Assert on array elements
98 assert(isset($nodeData[self::DB_COLUMN_NODE_ID]));
101 $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class');
103 // Search for node id and limit it to one entry
104 $searchInstance->addCriteria(self::DB_COLUMN_NODE_ID, $nodeData[self::DB_COLUMN_NODE_ID]);
105 $searchInstance->setLimit(1);
108 return $searchInstance;
112 * Prepares a "local" instance of a StoreableCriteria class with all node
113 * data for insert/update queries. This data set contains data from *this*
116 * @return $dataSetInstance An instance of a StoreableCriteria class
118 private function prepareLocalDataSetInstance () {
119 // Get node/request instances
120 $nodeInstance = Registry::getRegistry()->getInstance('node');
121 $requestInstance = ApplicationHelper::getSelfInstance()->getRequestInstance();
123 // Get a dataset instance
124 $dataSetInstance = ObjectFactory::createObjectByConfiguredName('dataset_criteria_class', array(self::DB_TABLE_NODE_DHT));
126 // Set the primary key
127 $dataSetInstance->setUniqueKey(self::DB_COLUMN_NODE_ID);
129 // Get ip:port combination and "explode" it
130 $ipPort = $nodeInstance->getAddressPortArray();
132 // Make sure both is valid
133 assert(($ipPort[0] !== 'invalid') && ($ipPort[1] !== 'invalid'));
135 // Get an array of all accepted object types
136 $objectList = $nodeInstance->getListFromAcceptedObjectTypes();
138 // Make sure this is an array
139 assert(is_array($objectList));
141 // Add public node data
142 $dataSetInstance->addCriteria(self::DB_COLUMN_NODE_MODE , $requestInstance->getRequestElement('mode'));
143 $dataSetInstance->addCriteria(self::DB_COLUMN_EXTERNAL_IP , $ipPort[0]);
144 $dataSetInstance->addCriteria(self::DB_COLUMN_LISTEN_PORT , $ipPort[1]);
145 $dataSetInstance->addCriteria(self::DB_COLUMN_NODE_ID , $nodeInstance->getNodeId());
146 $dataSetInstance->addCriteria(self::DB_COLUMN_SESSION_ID , $nodeInstance->getSessionId());
147 $dataSetInstance->addCriteria(self::DB_COLUMN_PRIVATE_KEY_HASH, $nodeInstance->getPrivateKeyHash());
148 $dataSetInstance->addCriteria(self::DB_COLUMN_ACCEPTED_OBJECTS, implode(BaseHubNode::OBJECT_LIST_SEPARATOR, $objectList));
151 return $dataSetInstance;
155 * Checks whether the local (*this*) node is registered in the DHT by
156 * checking if the external ip/port is found.
158 * @return $isRegistered Whether *this* node is registered in the DHT
160 public function isLocalNodeRegistered () {
162 if (!isset($GLOBALS[__METHOD__])) {
163 // Get a search criteria instance
164 $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class');
167 $nodeInstance = Registry::getRegistry()->getInstance('node');
169 // Get ip:port combination and "explode" it
170 $ipPort = $nodeInstance->getAddressPortArray();
173 * Make sure both is not 'invalid' which means that the resolver
176 assert(($ipPort[0] !== 'invalid') && ($ipPort[1] !== 'invalid'));
178 // Add ip:port/node id as criteria
179 $searchInstance->addCriteria(self::DB_COLUMN_EXTERNAL_IP, $ipPort[0]);
180 $searchInstance->addCriteria(self::DB_COLUMN_LISTEN_PORT, $ipPort[1]);
181 $searchInstance->addCriteria(self::DB_COLUMN_NODE_ID , $nodeInstance->getNodeId());
182 $searchInstance->setLimit(1);
184 // Query database and get a result instance back
185 $resultInstance = $this->doSelectByCriteria($searchInstance);
187 // Cache result of if there is an entry, valid() will tell us if an entry is there
188 $GLOBALS[__METHOD__] = $resultInstance->valid();
192 return $GLOBALS[__METHOD__];
196 * Registeres the local (*this*) node with its data in the DHT.
200 public function registerLocalNode () {
201 // Assert to make sure this method is called with no record in DB (the actual backend of the DHT)
202 assert(!$this->isLocalNodeRegistered());
204 // Get prepared data set instance
205 $dataSetInstance = $this->prepareLocalDataSetInstance();
207 // "Insert" this dataset instance completely into the database
208 $this->queryInsertDataSet($dataSetInstance);
212 * Updates local (*this*) node data in DHT, this is but not limited to the
213 * session id, ip number (and/or hostname) and port number.
217 public function updateLocalNode () {
218 // Assert to make sure this method is called with one record in DB (the actual backend of the DHT)
219 assert($this->isLocalNodeRegistered());
222 $nodeInstance = Registry::getRegistry()->getInstance('node');
224 // Get search criteria
225 $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class');
227 // Search for node id and limit it to one entry
228 $searchInstance->addCriteria(self::DB_COLUMN_NODE_ID, $nodeInstance->getNodeId());
229 $searchInstance->setLimit(1);
231 // Get a prepared dataset instance
232 $dataSetInstance = $this->prepareLocalDataSetInstance();
234 // Set search instance
235 $dataSetInstance->setSearchInstance($searchInstance);
237 // Update DHT database record
238 $this->queryUpdateDataSet($dataSetInstance);
242 * Finds a node locally by given session id
244 * @param $sessionId Session id to lookup
245 * @return $nodeData Node data array
247 public function findNodeLocalBySessionId ($sessionId) {
248 // Get search criteria
249 $searchInstance = ObjectFactory::createObjectByConfiguredName('search_criteria_class');
251 // Search for session id and limit it to one entry
252 $searchInstance->addCriteria(self::DB_COLUMN_SESSION_ID, $sessionId);
253 $searchInstance->setLimit(1);
255 // Query database and get a result instance back
256 $resultInstance = $this->doSelectByCriteria($searchInstance);
258 // Return result instance
259 return $resultInstance;
263 * Registeres a node by given message data.
265 * @param $messageData An array of all message data
266 * @param $handlerInstance An instance of a Handleable class
269 public function registerNodeByMessageData (array $messageData, Handleable $handlerInstance) {
270 // Get a data set instance
271 $dataSetInstance = ObjectFactory::createObjectByConfiguredName('dataset_criteria_class', array(self::DB_TABLE_NODE_DHT));
273 // Set primary key (session id)
274 $dataSetInstance->setUniqueKey(self::DB_COLUMN_SESSION_ID);
276 // Add all array elements
277 $handlerInstance->addArrayToDataSet($dataSetInstance, $messageData);
279 // Remove 'node_list'
280 $dataSetInstance->unsetCriteria(self::DB_COLUMN_NODE_LIST);
282 // Run the "INSERT" query
283 $this->queryInsertDataSet($dataSetInstance);
287 * Updates an existing entry in node list
289 * @param $messageData An array of all message data
290 * @param $handlerInstance An instance of a Handleable class
291 * @param $searchInstance An instance of LocalSearchCriteria class
294 public function updateNodeByMessageData (array $messageData, Handleable $handlerInstance, LocalSearchCriteria $searchInstance) {
295 // Get a data set instance
296 $dataSetInstance = ObjectFactory::createObjectByConfiguredName('dataset_criteria_class', array(self::DB_TABLE_NODE_DHT));
298 // Add search instance
299 $dataSetInstance->setSearchInstance($searchInstance);
301 // Set primary key (session id)
302 $dataSetInstance->setUniqueKey(self::DB_COLUMN_SESSION_ID);
304 // Add all array elements
305 $handlerInstance->addArrayToDataSet($dataSetInstance, $messageData);
307 // Remove 'node_list'
308 $dataSetInstance->unsetCriteria(self::DB_COLUMN_NODE_LIST);
310 // Run the "UPDATE" query
311 $this->queryUpdateDataSet($dataSetInstance);
315 * Determines whether the given node data is already inserted in the DHT
317 * @param $nodeData An array with valid node data
318 * @return $isRegistered Whether the given node data is already inserted
320 public function isNodeRegistered (array $nodeData) {
321 // Assert on array elements
322 assert(isset($nodeData[self::DB_COLUMN_NODE_ID]));
324 // Get search criteria
325 $searchInstance = $this->prepareSearchInstance($nodeData);
327 // Query database and get a result instance back
328 $resultInstance = $this->doSelectByCriteria(
331 // Only look for these array elements ("keys")
333 self::DB_COLUMN_NODE_ID => TRUE,
334 self::DB_COLUMN_EXTERNAL_IP => TRUE,
335 self::DB_COLUMN_LISTEN_PORT => TRUE,
339 // Check if there is an entry
340 $isRegistered = $resultInstance->valid();
342 // Return registration status
343 return $isRegistered;
347 * Registers a node with given data in the DHT. If the node is already
348 * registered this method shall throw an exception.
350 * @param $nodeData An array with valid node data
352 * @throws NodeAlreadyRegisteredException If the node is already registered
354 public function registerNode (array $nodeData) {
355 // Assert on array elements
356 assert(isset($nodeData[self::DB_COLUMN_NODE_ID]));
358 // Is the node registered?
359 if ($this->isNodeRegistered($nodeData)) {
360 // Throw an exception
361 throw new NodeAlreadyRegisteredException(array($this, $nodeData), self::EXCEPTION_NODE_ALREADY_REGISTERED);
364 // @TODO Unimplemented part
365 $this->partialStub('nodeData=' . print_r($nodeData, TRUE));
369 * Updates a node's entry in the DHT with given data. This will enrich or
370 * just update already exsiting data. If the node is not found this method
371 * shall throw an exception.
373 * @param $nodeData An array with valid node data
375 * @throws NodeDataMissingException If the node's data is missing
377 public function updateNode (array $nodeData) {
378 // Assert on array elements
379 assert(isset($nodeData[self::DB_COLUMN_NODE_ID]));
381 // Is the node registered?
382 if (!$this->isNodeRegistered($nodeData)) {
383 // No, then throw an exception
384 throw new NodeDataMissingException(array($this, $nodeData), self::EXCEPTION_NODE_NOT_REGISTERED);
387 // Get a search instance
388 $searchInstance = $this->prepareSearchInstance($nodeData);
390 // Get a data set instance
391 $dataSetInstance = ObjectFactory::createObjectByConfiguredName('dataset_criteria_class', array(self::DB_TABLE_NODE_DHT));
393 // Add search instance
394 $dataSetInstance->setSearchInstance($searchInstance);
396 // Set primary key (session id)
397 $dataSetInstance->setUniqueKey(self::DB_COLUMN_SESSION_ID);
400 $nodeInstance = Registry::getRegistry()->getInstance('node');
402 // Add all array elements
403 $nodeInstance->addArrayToDataSet($dataSetInstance, $nodeData);
405 // Remove 'node_list'
406 $dataSetInstance->unsetCriteria(self::DB_COLUMN_NODE_LIST);
408 // Run the "UPDATE" query
409 $this->queryUpdateDataSet($dataSetInstance);