First commit
[anna.git] / source / dbms / Database.cpp
1 // ANNA - Anna is Not 'N' Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // https://bitbucket.org/testillano/anna
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     * Neither the name of Google Inc. nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36
37 #include <stdlib.h>
38 #include <locale.h>
39
40 #include <string>
41 #include <algorithm>
42
43 #include <anna/core/tracing/TraceMethod.hpp>
44
45 #include <anna/xml/Node.hpp>
46 #include <anna/xml/Attribute.hpp>
47
48 #include <anna/comm/INetAddress.hpp>
49
50 #include <anna/dbms/Database.hpp>
51 #include <anna/dbms/Statement.hpp>
52 #include <anna/dbms/FailRecoveryHandler.hpp>
53 #include <anna/dbms/internal/sccs.hpp>
54 #include <anna/dbms/Float.hpp>
55 #include <anna/dbms/StatementTranslator.hpp>
56
57 using namespace std;
58 using namespace anna;
59 using namespace anna::dbms;
60
61 Database::Database(const char* className, const char* dbmsName) :
62   Component(className),
63   a_name((dbmsName == NULL) ? "local" : dbmsName),
64   a_type((dbmsName == NULL) ? Type::Local : Type::Remote),
65   a_failRecoveryHandler(NULL),
66   a_statementTranslator(NULL) {
67   dbms::sccs::activate();
68 }
69
70 Database::~Database() {
71   stop();
72 }
73
74 void Database::do_initialize()
75 throw(RuntimeException) {
76   LOGMETHOD(TraceMethod tm("dbms::Database", "do_initialize", ANNA_FILE_LOCATION));
77   int counter(0);
78   bool error = false;
79
80   for(connection_iterator iic = connection_begin(), maxiic = connection_end(); iic != maxiic; iic ++) {
81     try {
82       connection(iic)->open();
83       counter ++;
84     } catch(Exception& ex) {
85       ex.trace();
86       error = true;
87     }
88   }
89
90   if(counter == 0 && error == true) {
91     string msg(asString());
92     msg += " | No available connections";
93     throw RuntimeException(msg, ANNA_FILE_LOCATION);
94   }
95
96   LOGINFORMATION(
97     Logger::information(asString(), ANNA_FILE_LOCATION);
98   );
99 }
100
101 void Database::do_stop()
102 throw() {
103   LOGMETHOD(TraceMethod tm("dbms::Database", "do_stop", ANNA_FILE_LOCATION));
104
105   try {
106     Connection* _connection;
107
108     for(connection_iterator iic = connection_begin(), maxiic = connection_end(); iic != maxiic; iic ++) {
109       _connection = connection(iic);
110       _connection->close();
111       delete _connection;
112     }
113
114     a_connections.clear();
115   } catch(Exception& ex) {
116     ex.trace();
117     a_connections.clear();
118   }
119 }
120
121 /**
122  * Para evitar que todos los clones usen la misma conexion a la base de datos, se realiza en
123  * cada uno de ellos una re-conexion, es decir, se cierra la original (abierta por el proceso
124  * padre) y se abre una nueva conexion con los mismos parametros y contra la misma base de datos.
125  */
126 void Database::do_cloneChild()
127 throw(RuntimeException) {
128   LOGMETHOD(TraceMethod tm("dbms::Database", "do_cloneChild", ANNA_FILE_LOCATION));
129
130   for(connection_iterator ii = connection_begin(), maxii = connection_end(); ii != maxii; ii ++) {
131     dbms::Connection* conn = connection(ii);
132     LOGDEBUG(
133       string msg("dbms::Database::do_cloneChild | ");
134       msg += conn->asString();
135       Logger::debug(msg, ANNA_FILE_LOCATION);
136     );
137     recover(*conn, 0);
138   }
139 }
140
141 Connection* Database::createConnection(const char* name, const char* user, const char* password)
142 throw(RuntimeException, DatabaseException) {
143   Guard guard(this, "dbms::Database (createConnection)");
144
145   if(a_connections.size() >= MaxConnection) {
146     string msg("Database::createConnection | ");
147     msg += asString();
148     msg += functions::asText(" | Cannot create more than %d connections per database", MaxConnection);
149     throw RuntimeException(msg, ANNA_FILE_LOCATION);
150   }
151
152   for(connection_iterator ii = connection_begin(), maxii = connection_end(); ii != maxii; ii ++) {
153     if(connection(ii)->getName() == name) {
154       string msg("Database::createConnection | ");
155       msg += asString();
156       msg += " | Connection: ";
157       msg += name;
158       msg += " | Previously registered";
159       throw RuntimeException(msg, ANNA_FILE_LOCATION);
160     }
161   }
162
163   string strname(name);
164   Connection* result = allocateConnection(strname, user, password);
165
166   if(result == NULL) {
167     string msg(asString());
168     msg += " | ";
169     msg += strname;
170     msg += " | Unable to instance connection";
171     throw RuntimeException(msg, ANNA_FILE_LOCATION);
172   }
173
174   LOGDEBUG(
175     string msg("dbms::Database::createConnection | ");
176     msg += result->asString();
177     Logger::write(Logger::Debug, msg, ANNA_FILE_LOCATION);
178   );
179
180   if(getState() == Component::State::Running) {
181     try {
182       result->open();
183       a_connections.push_back(result);
184     } catch(RuntimeException& ex) {
185       ex.trace();
186       delete result;
187       throw;
188     } catch(DatabaseException& edbms) {
189       edbms.trace();
190       delete result;
191       throw;
192     }
193   } else
194     a_connections.push_back(result);
195
196   return result;
197 }
198
199 Connection& Database::findConnection(const char* name)
200 throw(RuntimeException) {
201   Guard guard(this, "dbms::Database (findConnection)");
202   Connection* result = NULL;
203
204   for(connection_iterator ii = connection_begin(), maxii = connection_end(); ii != maxii; ii ++) {
205     if(anna_strcmp(connection(ii)->getName().c_str(), name) == 0) {
206       result = connection(ii);
207       break;
208     }
209   }
210
211   if(result == NULL) {
212     string msg("Database::findConnection | ");
213     msg += asString();
214     msg += " | Conexion: ";
215     msg += name;
216     msg += " | Unregistered";
217     throw RuntimeException(msg, ANNA_FILE_LOCATION);
218   }
219
220   if(result->isAvailable() == false || result->isEnabled() == false) {
221     string msg("Database::findConnection | ");
222     msg += asString();
223     msg += " | Connection: ";
224     msg += name;
225     msg += " | Unavailable";
226     throw RuntimeException(msg, ANNA_FILE_LOCATION);
227   }
228
229   LOGDEBUG(
230     string msg("Database::findConnection | ");
231     msg += result->asString();
232     Logger::debug(msg, ANNA_FILE_LOCATION);
233   );
234   return *result;
235 }
236
237 Statement* Database::createStatement(const char* name, const char* expression, const bool isCritical)
238 throw(RuntimeException) {
239   if(findStatement(name) != NULL)
240     throw RuntimeException(functions::asString("Sentence: %s | Name already in use", name), ANNA_FILE_LOCATION);
241
242   Guard guard(this, "dbms::Database::createStatement");
243
244   if(a_statementTranslator != NULL)
245     expression = a_statementTranslator->apply(expression);
246
247   Statement* result = allocateStatement(name, expression, isCritical);
248   LOGDEBUG(
249     string msg("dbms::Database::createStatement | ");
250     msg += result->asString();
251
252   if(a_statementTranslator != NULL) {
253   msg += " | Translator: ";
254   msg += a_statementTranslator->getName();
255   }
256   Logger::debug(msg, ANNA_FILE_LOCATION);
257   );
258   a_statements.push_back(result);
259   return result;
260 }
261
262 Statement* Database::findStatement(const char* name)
263 throw() {
264   Guard guard(this, "dbms::Database::findStatement");
265   vector <Statement*>::iterator ii, maxii;
266   Statement* result(NULL);
267
268   for(ii = a_statements.begin(), maxii = a_statements.end(); ii != maxii; ii ++) {
269     if(anna_strcmp((*ii)->getName().c_str(), name) == 0) {
270       result = *ii;
271       break;
272     }
273   }
274
275   return result;
276 }
277
278 void Database::releaseStatement(Statement* statement)
279 throw() {
280   if(statement == NULL) {
281     Logger::write(Logger::Warning, asString(), "Cannot release a NULL SQL sentence", ANNA_FILE_LOCATION);
282     return;
283   }
284
285   LOGDEBUG(
286     string msg("dbms::Database::releaseStatement | ");
287     msg += statement->asString();
288     Logger::debug(msg, ANNA_FILE_LOCATION);
289   );
290   Guard guard(this, "dbms::Database::releaseStatement");
291   vector <Statement*>::iterator end = a_statements.end();
292   vector <Statement*>::iterator ii = find(a_statements.begin(), end, statement);
293
294   if(ii != end) {
295     a_statements.erase(ii);
296     delete statement;
297   }
298 }
299
300 void Database::recover(Connection& connection, const int tryCounter)
301 throw(RuntimeException) {
302   try {
303     connection.close();
304     connection.open();
305   } catch(DatabaseException& edbms) {
306     edbms.trace();
307
308     if(a_failRecoveryHandler != NULL)
309       a_failRecoveryHandler->apply(connection, tryCounter);
310   }
311 }
312
313 string Database::asString() const
314 throw() {
315   string result("dbms::Database { ");
316   result += Component::asString();
317
318   if(a_type ==  Type::Local)
319     result += " | Type: Local";
320   else {
321     result += " | Type: Remote | Name: ";
322     result += a_name;
323   }
324
325   return result += " }";
326 }
327
328 xml::Node* Database::asXML(xml::Node* parent) const
329 throw() {
330   parent = Component::asXML(parent);
331   xml::Node* result = parent->createChild("dbms.Database");
332   xml::Node* node;
333   result->createAttribute("Type", (a_type == Type::Local) ? "Local" : "Remote");
334
335   if(a_type != Type::Local)
336     result->createAttribute("Name", a_name);
337
338   if(a_statementTranslator != NULL)
339     result->createAttribute("Translator", a_statementTranslator->getName());
340
341   node = result-> createChild("Connections");
342
343   for(const_connection_iterator ii = connection_begin(), maxii = connection_end(); ii != maxii; ii ++)
344     connection(ii)->asXML(node);
345
346   node = result-> createChild("Statements");
347
348   for(const_statement_iterator ii = statement_begin(), maxii = statement_end(); ii != maxii; ii ++)
349     statement(ii)->asXML(node);
350
351   return result;
352 }
353