First commit
[anna.git] / include / anna / comm / ByRangeDelivery.hpp
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 #ifndef anna_comm_ByRangeDelivery_hpp
38 #define anna_comm_ByRangeDelivery_hpp
39
40 #include <anna/core/functions.hpp>
41 #include <anna/core/tracing/Logger.hpp>
42
43 #include <anna/xml/Node.hpp>
44 #include <anna/xml/Attribute.hpp>
45
46 #include <anna/comm/RoundRobinDelivery.hpp>
47
48 namespace anna {
49
50 namespace comm {
51 class Resource;
52 }
53
54 namespace comm {
55
56 /**
57    Servicio de reparto de carga permite selecionar el servidor al que enviar la peticion en base al valor de una
58    clave previamente indicada.
59
60    A diferencia de los comm::Service habituales la asociacion entre el servicio y el proceso no debe hacerse
61    mediante comm::Service::attach, sino mediante comm::ByRangeDelivery::attach.
62
63    \param TKey Tipo de clave usada para indexar los servicios de reparto.
64    \param TDelivery: Tipo de reparto usado en cada rango.
65    \param TSerializer: Clase que es capaz de convertir la \em TKey en una cadena. Debe implementar un metodo
66    \em asString que recibe una instancia de TKey y devuelva una cadena.
67
68    La clase \em Delivery debe implementar los mismos metodos que un comm::Service.
69 */
70 template < typename TKey, typename TDelivery = RoundRobinDelivery, typename TSerializer = anna::functions > class ByRangeDelivery : public comm::Service {
71 protected:
72   struct Range {
73     const TKey bottom;
74     const TKey top;
75     TDelivery* delivery;
76
77     Range(const std::string& baseName, const TKey& _bottom, const TKey& _top) :
78       bottom(_bottom), top(_top) {
79       std::string name(baseName);
80       name += '[';
81       name += TSerializer::asString(_bottom);
82       name += ',';
83       name += TSerializer::asString(_top);
84       name += ']';
85       delivery = new TDelivery(name.c_str(), false);
86     }
87
88     std::string asString() const throw() {
89       std::string result("comm::ByRangeDelivery { Bottom: ");
90       result += TSerializer::asString(bottom);
91       result += " | Top: ";
92       result += TSerializer::asString(top);
93       result += " | ";
94       result += delivery->asString();
95       return result += " }";
96     }
97
98     xml::Node* asXML(xml::Node* parent) const throw() {
99       xml::Node* result = parent->createChild("comm.Range");
100       result->createAttribute("Bottom", TSerializer::asString(bottom));
101       result->createAttribute("Top", TSerializer::asString(top));
102       delivery->asXML(result);
103       return result;
104     }
105   };
106
107 public:
108   typedef std::vector<Range*> range_container;
109   typedef typename range_container::iterator range_iterator;
110   typedef typename range_container::const_iterator const_range_iterator;
111
112   /**
113      Constructor.
114      \param name Nombre logico del servicio de reparto de carga.
115      \param isCritical Indica las caracteristicas del reparto de carga. Debe valor \em true si el
116      reparto de carga a crear es critico o \em false en otro caso.
117   */
118   ByRangeDelivery(const char* name, const bool isCritical) :
119     comm::Service(name, isCritical),
120     a_currentRange(NULL)
121   {;}
122
123   /**
124      Destructor.
125   */
126   virtual ~ByRangeDelivery() {
127     for(range_iterator ii = range_begin(), maxii = range_end(); ii != maxii; ii ++)
128       delete range(ii)->delivery;
129
130     a_ranges.clear();
131   }
132
133   /**
134      Instancia un rango de reparto con los parametros definidos como parametros. Una vez que
135      obtengamos el comm::Delivery asociado al rango creado debemos asociar tantos comm::Resource
136      como sea necesario.
137
138      \param bottom Valor minimo del rango.
139      \param top Valor maximo del rango.
140      \return La instancia del servicio de reparto asociado a este rango.
141   */
142   range_iterator createRange(const TKey& bottom, const TKey& top)
143   throw(RuntimeException) {
144     Range* result;
145
146     if(bottom > top) {
147       std::string msg(asString());
148       msg += " | Bottom: ";
149       msg += TSerializer::asString(bottom);
150       msg += " | Invalid range (bottom > top)";
151       throw RuntimeException(msg, ANNA_FILE_LOCATION);
152     }
153
154     if((result = find(bottom)) != NULL) {
155       std::string msg(asString());
156       msg += " | Bottom: ";
157       msg += TSerializer::asString(bottom);
158       msg += " | Overlapped with ";
159       msg += result->asString();
160       throw RuntimeException(msg, ANNA_FILE_LOCATION);
161     }
162
163     if((result = find(top)) != NULL) {
164       std::string msg(asString());
165       msg += " | top: ";
166       msg += TSerializer::asString(top);
167       msg += " | Overlapped with ";
168       msg += result->asString();
169       throw RuntimeException(msg, ANNA_FILE_LOCATION);
170     }
171
172     a_ranges.push_back(result = new Range(getName(), bottom, top));
173     return range_begin() + a_ranges.size() - 1;
174   }
175
176   /**
177      Asocia el comm::Server recibido como parametro al rango representado por el iterador recibido.
178      \param ii Iterador de rango al que asociar el servicio. Debe ser resultado de invocar a #createRange.
179      \param server Instancia del comm::Server a incorporar dentro del rango.
180   */
181   void attach(range_iterator& ii, Server* server) throw(RuntimeException) {
182     range(ii)->delivery->attach(server);
183     comm::Service::attach(server);
184   }
185
186   /**
187      Establece la clave que se usara para decidir que pareja de maquinas usar para enviar el mensaje.
188      \param key Clave usada para calcular el reparto.
189      \warning Este metodo debe invocarse siempre antes de usar este servicio de reparto.
190   */
191   void prepare(const TKey& key) throw(RuntimeException) {
192     Range* w = find(key);
193
194     if(w == NULL && Logger::isActive(Logger::Warning)) {
195       std::string msg(asString());
196       msg += " | TKey: ";
197       msg += TSerializer::asString(key);
198       msg += " | No range associated";
199       Logger::warning(msg, ANNA_FILE_LOCATION);
200     } else
201       a_currentRange = w;
202
203     if(a_currentRange == NULL) {
204       std::string msg(asString());
205       msg += " | TKey: ";
206       msg += TSerializer::asString(key);
207       msg += " | No delivery service associated has been found";
208       throw RuntimeException(msg, ANNA_FILE_LOCATION);
209     }
210   }
211
212   std::string asString() const
213   throw() {
214     std::string result = className();
215     result += " { ";
216     result += comm::Delivery::asString();
217     result += " | Ranges: ";
218
219     for(const_range_iterator ii = range_begin(), maxii = range_end(); ii != maxii; ii ++)  {
220       result += "\n\t";
221       result += range(ii)->asString();
222     }
223
224     return result += "}";
225   }
226
227   xml::Node* asXML(xml::Node* parent) const
228   throw() {
229     xml::Node* result = parent->createChild("anna.comm.ByRangeDelivery");
230     xml::Node* node = comm::Service::asXML(result);
231
232     for(const_range_iterator ii = range_begin(), maxii = range_end(); ii != maxii; ii ++)
233       range(ii)->asXML(node);
234
235     return result;
236   }
237
238   /**
239      Metodo que devuelve el nombre completo de este selector de recursos.
240      Para evitar ambigüedades este nombre incluye la lista completa de \em namespaces
241      a los que pertenece la clase.
242      \return Una cadena con el nombre de este selector.
243   */
244   static const char* className() throw() { return "anna::comm::ByRangeDelivery"; }
245
246 protected:
247   range_iterator range_begin() throw() { return a_ranges.begin(); }
248   range_iterator range_end() throw() { return a_ranges.end(); }
249
250   const_range_iterator range_begin() const throw() { return a_ranges.begin(); }
251   const_range_iterator range_end() const throw() { return a_ranges.end(); }
252
253   static Range* range(range_iterator& ii) throw() { return *ii; }
254   static const Range* range(const_range_iterator& ii) throw() { return *ii; }
255
256   virtual void do_initialize()
257   throw(RuntimeException) {
258     for(range_iterator ii = range_begin(), maxii = range_end(); ii != maxii; ii ++)
259       range(ii)->delivery->initialize();
260   }
261
262 private:
263   range_container a_ranges;
264   Range* a_currentRange;
265
266   Range* find(const TKey& key) throw() {
267     Range* result(NULL);
268     Range* w;
269
270     for(range_iterator ii = range_begin(), maxii = range_end(); ii != maxii; ii ++) {
271       w = range(ii);
272
273       if(w->bottom <= key && key <= w->top) {
274         result = w;
275         break;
276       }
277     }
278
279     return result;
280   }
281
282   comm::Resource* do_apply()
283   throw(RuntimeException) {
284     if(a_currentRange == NULL) {
285       std::string msg(asString());
286       msg += " | Wrong call to anna::comm::ByRangeDelivery::prepare";
287       throw RuntimeException(msg, ANNA_FILE_LOCATION);
288     }
289
290     return a_currentRange->delivery->apply();
291   }
292
293   // Considera que el servicio esta NO-disponible cuando TODOS los servicios fallan
294   bool do_fault(const comm::Resource* resource)
295   throw() {
296     int n, nfault;
297     n = nfault = 0;
298
299     for(range_iterator ii = range_begin(), maxii = range_end(); ii != maxii; ii ++) {
300       n ++;
301
302       if(range(ii)->delivery->fault(resource) == true)
303         nfault ++;
304     }
305
306     return (n == nfault);
307   }
308
309   // Considera que el servicio esta recuperado cuando alguno de los servicios esta disponible
310   bool do_recover(const comm::Resource* resource)
311   throw() {
312     bool result(false);
313
314     for(range_iterator ii = range_begin(), maxii = range_end(); ii != maxii; ii ++) {
315       if(range(ii)->delivery->recover(resource) == true)
316         result = true;
317     }
318
319     return result;
320   }
321 };
322
323 }
324 }
325
326 #endif
327