Added some debug info
[gnucomo.git] / src / gcm_input / message.cpp
1
2 /**************************************************************************
3 **  (c) Copyright 2002, Andromeda Technology & Automation
4 ** This is free software; you can redistribute it and/or modify it under the
5 ** terms of the GNU General Public License, see the file COPYING.
6 ***************************************************************************
7 ** MODULE INFORMATION *
8 ***********************
9 **      FILE NAME      : message.cpp
10 **      SYSTEM NAME    : Gnucomo - Gnu Computer Monitoring
11 **      VERSION NUMBER : $Revision: 1.19 $
12 **
13 **  DESCRIPTION      :  Implementation of the message handling classes
14 **
15 **  EXPORTED OBJECTS : 
16 **  LOCAL    OBJECTS : 
17 **  MODULES  USED    :
18 ***************************************************************************
19 **  ADMINISTRATIVE INFORMATION *
20 ********************************
21 **      ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
22 **      CREATION DATE   : Sep 16, 2002
23 **      LAST UPDATE     : Nov 02, 2007
24 **      MODIFICATIONS   : 
25 **************************************************************************/
26
27 /*****************************
28    $Log: message.cpp,v $
29    Revision 1.19  2011-03-24 10:20:37  arjen
30    Added some debug info
31
32    Revision 1.18  2007/11/03 10:23:53  arjen
33    Handling of parameters is greatly improved.
34    When creating a new parameter from an XML report which is fed into
35    gcm_input, the class definition is used as a template to fill in
36    the default values for the properties.
37    The class is also used as a template when a new property is added
38    to an existing parameter.
39
40    DYNAMIC properties are now handled properly. Instead of making a
41    'changed property' notification, the value is checked against the
42    defined range for the property. An 'out of range' notification
43    is created when this condition is detected.
44
45    Revision 1.17  2005/05/31 05:51:41  arjen
46    Textual changes in parameter notifications
47
48    Revision 1.16  2003/12/04 10:38:09  arjen
49    Major redesign. All input is handled through XML. Raw input data is first
50    transformed into an XML document for further processing.
51    A collection of polymorphic classes handle the transformation of various
52    input formats into XML.
53    Classifying input data is done with a finite improbability calculation.
54
55    Revision 1.15  2003/10/27 11:28:27  arjen
56    Do not add another parameter_notification record is the notification
57    already exists for that parameter.
58
59    Revision 1.14  2003/09/01 06:57:14  arjen
60    Reject log entries that are found to be invalid.
61
62    Revision 1.13  2003/08/16 15:28:45  arjen
63    Fixed a namespace problem
64
65    Revision 1.12  2003/08/11 16:56:16  arjen
66    Different kinds of log files are parsed by a collection of objects
67    of different classes, derived from the base class line_cooker
68    Depending on the message content or the message_type element in
69    XML, one of these objects is selected.
70
71    Logrunner is integrated with gcm_input. Although its functionality
72    is still limited, a connection between logrunner and gcm_input
73    is beginning to form.
74
75    Revision 1.11  2003/08/05 08:15:00  arjen
76    Debug output to the log stream instead of cerr.
77    Fixed namespace problems in XPath searches of the DOM.
78    Moved string utility functions to a separate file.
79
80    Revision 1.10  2003/04/29 09:16:44  arjen
81    Read XML input,
82    Only cooked log entries for now.
83
84    Revision 1.9  2003/03/29 09:04:10  arjen
85    Extract the hostname out of the 'From:' or 'Message-Id:' line
86    of an email header.
87
88    Revision 1.8  2003/03/16 09:42:40  arjen
89    Read IRIX system logs.
90
91    Revision 1.7  2003/02/21 08:08:05  arjen
92    Gcm_input also detects packages that are removed from the system.
93    Determining the version number of a package in a RPM
94    list is improved. Only the last one or two parts of the string that
95    begin with a '-' and a number are considered the version.
96
97    Revision 1.6  2003/02/05 09:37:51  arjen
98    Create notifications when a new package is discovered
99    in a 'rpm -qa' list or when the version of a package is changed.
100
101    Revision 1.4  2002/12/06 22:26:28  arjen
102    Set the value of log.processed to FALSE when inserting a
103    new log entry into the database
104    When a syslog entry arrives from last year, gcm_input subtracts one from the
105    year of arrival to create the year of the log entry.
106    Read output from "rpm -qa" and enter packages in the parameter table.
107
108    Revision 1.3  2002/11/09 08:04:27  arjen
109    Added a reference to the GPL
110
111    Revision 1.2  2002/11/04 10:13:36  arjen
112    Use proper namespace for iostream classes
113
114    Revision 1.1  2002/10/05 10:25:49  arjen
115    Creation of gcm_input and a first approach to a web interface
116
117 *****************************/
118
119 static const char *RCSID = "$Id: message.cpp,v 1.19 2011-03-24 10:20:37 arjen Exp $";
120
121 #include <algorithm>
122 #include <libxml/xpath.h>
123 #include <libxml/debugXML.h>
124 #include "message.h"
125
126 //#define DEBUG
127
128 extern bool verbose;   /*  Defined in the main application */
129 extern bool testmode;
130 extern bool incremental;
131 extern std::ostream *Log;
132
133 /*   Utility functions   */
134
135 extern String SQL_Escape(String s);
136
137 /*=========================================================================
138 **  NAME           : operator >>
139 **  SYNOPSIS       : bool operator >> (message_buffer &, String &)
140 **  PARAMETERS     : 
141 **  RETURN VALUE   : True if input was available.
142 **
143 **  DESCRIPTION    : Input operator. Read the next line from the message.
144 **
145 **  VARS USED      :
146 **  VARS CHANGED   :
147 **  FUNCTIONS USED :
148 **  SEE ALSO       :
149 **  LAST MODIFIED  : Nov 04, 2002
150 **=========================================================================
151 */
152
153 bool operator >> (message_buffer &b, String &s)
154 {
155    bool   input_ok = false;
156
157    if (b.next_line == b.buffer.end())
158    {
159       String   l;
160
161       if (*(b.input) >> l)
162       {
163          b.buffer.push_back(l);
164
165          //   next_line keeps pointing to the end.
166
167          s = l;
168          input_ok = true;
169       }
170    }
171    else
172    {
173       s = *(b.next_line);
174       b.next_line++;
175       input_ok = true;
176    }
177    return input_ok;
178 }
179
180 /*=========================================================================
181 **  NAME           : client_message
182 **  SYNOPSIS       : client_message(std::istream *in, gnucomo_database db)
183 **  PARAMETERS     : 
184 **  RETURN VALUE   : None
185 **
186 **  DESCRIPTION    : Client message constructor.
187 **
188 **  VARS USED      :
189 **  VARS CHANGED   :
190 **  FUNCTIONS USED :
191 **  SEE ALSO       :
192 **  LAST MODIFIED  : Nov 04, 2002
193 **=========================================================================
194 */
195
196 client_message::client_message(std::istream *in, gnucomo_database db)
197 {
198    input.from(in);
199    database = db;
200
201    hostname = "";
202    mail_header   = false;
203    gpg_encrypted = false;
204    classification = UNKNOWN;
205    xmlDom         = NULL;
206    certainty      = 0.0;
207 }
208
209 static const String unix_date_re("[[:alpha:]]{3} [[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9]{4}");
210
211 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
212 static const regex re_dump("^ *DUMP: Date of this level");
213 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
214
215 static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
216 static const regex re_xml_header("xml .*\?>$");
217
218 /*=========================================================================
219 **  NAME           : extractHeader
220 **  SYNOPSIS       : void extractHeader()
221 **  PARAMETERS     : 
222 **  RETURN VALUE   : True if the mandatory header elements are available.
223 **
224 **  DESCRIPTION    : Extract the header information from the XML DOM tree.
225 **
226 **  VARS USED      :
227 **  VARS CHANGED   :
228 **  FUNCTIONS USED :
229 **  SEE ALSO       :
230 **  LAST MODIFIED  : Nov 26, 2003
231 **=========================================================================
232 */
233
234 bool client_message::extractHeader()
235 {
236    xmlNodePtr       root, item;
237    xmlNsPtr         namespaces[1];
238
239    xmlXPathObjectPtr res;
240    xmlXPathContextPtr pathcontext;
241
242    bool header_OK = true;
243
244    root = xmlDocGetRootElement(xmlDom);
245    namespaces[0] = root->ns;
246
247    //TODO Ought to check  root->name and  root->ns->href 
248
249    pathcontext = xmlXPathNewContext(xmlDom);
250    pathcontext->node = xmlDocGetRootElement(xmlDom);
251    pathcontext->namespaces = namespaces;
252    pathcontext->nsNr       = 1;
253
254 #ifdef DEBUG
255    xmlDebugDumpNodeList(stdout, pathcontext->node, 0);
256 #endif
257
258    res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:messagetype/text()", pathcontext);
259    if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
260    {
261       item = *res->nodesetval->nodeTab;
262
263       //  Select a line cooker based on the message type.
264
265 #ifdef DEBUG
266       *Log << "Looking for a line cooker for " << item->content << "\n";
267 #endif
268       pan.lc = 0;
269       std::list<xform>::iterator lci = kitchen.begin();
270       while (pan.lc == 0 && lci != kitchen.end())
271       {
272          if (lci->lc->message_type() == (const char *)(item->content))
273          {
274             pan.lc = lci->lc;
275          }
276          lci++;
277       }
278       if (pan.lc == 0)
279       {
280          *Log << "Can not find a line cooker for message type " << item->content << "\n";
281          header_OK = false;
282       }
283    }
284    else
285    {
286       *Log << "Message type not found in XML header.\n";
287       header_OK = false;
288    }
289
290    res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:hostname/text()", pathcontext);
291    if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
292    {
293       item = *res->nodesetval->nodeTab;
294       hostname = (const char *)item->content;
295    }
296    else
297    {
298       *Log <<  "Can not determine the hostname where the message came from.\n";
299       header_OK = false;
300    }
301
302    res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:service/text()", pathcontext);
303    if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
304    {
305       item = *res->nodesetval->nodeTab;
306       service = (const char *)item->content;
307    }
308    res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:time/text()", pathcontext);
309    if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
310    {
311       item = *res->nodesetval->nodeTab;
312       arrival = String((char *)item->content);
313       if (!arrival.proper())
314       {
315          *Log << "Arrival time is not properly stated.\n";
316          header_OK = false;
317       }
318
319    }
320    //xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
321
322    return header_OK;
323 }
324
325
326 /*=========================================================================
327 **  NAME           : classify
328 **  SYNOPSIS       : double classify(String host, date arriv_d, hour arriv_t, String serv)
329 **  PARAMETERS     : 
330 **  RETURN VALUE   : The certainty with which the message is classified.
331 **
332 **  DESCRIPTION    : 
333 **
334 **  VARS USED      :
335 **  VARS CHANGED   :
336 **  FUNCTIONS USED :
337 **  SEE ALSO       :
338 **  LAST MODIFIED  : Nov 27, 2003
339 **=========================================================================
340 */
341
342 double client_message::classify(String host, UTC arriv, String serv)
343 {
344    String    line;
345
346    hostname    = host;
347    arrival     = arriv;
348    service     = serv;
349
350    const double   epsilon = 0.1;   //  Threshold for uncertainty
351    const double   P       = 0.5;   //  Probability of a wrong match
352
353    double uncertainty;
354
355 #ifdef DEBUG
356    *Log << "Checking for a mail header.\n";
357 #endif
358
359    /*  First, check if the message has a mail header. */
360
361    if (input >> line && line == re_uxmail_from)
362    {
363       String    from_address;
364
365       mail_header = true;
366
367       /*  Skip the mail header until the first empty line.  */
368
369       while (input >> line && line != "")
370       {
371       }
372    }
373    else
374    {
375       //  Push the first line back, we need to read it again.
376       --input;
377
378    }
379
380    /*
381     *  Now that we have the mail header out of the way, try to figure
382     *  out what the content of the message is.
383     */
384
385 #ifdef DEBUG
386    *Log << "Classifying message.\n";
387 #endif
388
389
390    uncertainty = 1.0;
391
392    while (input >> line && uncertainty > epsilon)
393    {
394       if (verbose)
395       {
396          *Log << "  testing: " << line << "\n";
397       }
398
399       if (line == re_PGP)
400       {
401          uncertainty = 0.0;
402          gpg_encrypted = true;
403          *Log << "The message is PGP/GnuPG encrypted.\n";
404       }
405       else
406       {
407          //  Scan the list of line cookers if there is anything familiar.
408
409          std::list<xform>::iterator lci = kitchen.begin();
410          uncertainty = 1.0;
411          while (lci != kitchen.end())
412          {
413             if (lci->lc->check_pattern(line))
414             {
415                //  We have a match; decrease the uncertainty
416
417                lci->uncertainty *= P;
418                if (uncertainty > lci->uncertainty)
419                {
420                   uncertainty = lci->uncertainty;
421                   pan = *lci;
422                }
423                if (verbose)
424                {
425                   *Log << lci->lc->message_type() << " detected with "
426                        << lci->uncertainty << " uncertainty.\n";
427                }
428             }
429             lci++;
430          }
431          classification = COOKER_OBJECT;
432       }
433    }
434
435    input.rewind();
436
437    certainty = 1.0 - uncertainty;
438
439    return certainty;
440 }
441
442 /*=========================================================================
443 **  NAME           : enterXML
444 **  SYNOPSIS       : int enterXML()
445 **  PARAMETERS     : 
446 **  RETURN VALUE   : None
447 **
448 **  DESCRIPTION    : Analyze the DOM tree from the XML input.
449 **                   The DOM tree was previously parsed by readXMLinput().
450 **
451 **  VARS USED      :
452 **  VARS CHANGED   :
453 **  FUNCTIONS USED :
454 **  SEE ALSO       :
455 **  LAST MODIFIED  : Nov 02, 2007
456 **=========================================================================
457 */
458
459 struct param_property
460 {
461    String name;
462    String value;
463    String type;
464    double minimum;
465    double maximum;
466 };
467
468 void client_message::enterXML()
469 {
470    //TODO : return the number of elements that are handled.
471
472    xmlXPathObjectPtr res;
473    xmlXPathContextPtr pathcontext;
474    xmlNsPtr           namespaces[1];
475
476    /*  Try to find the host in the database */
477
478    String objectid;
479    String remark;        //  For notifications
480
481    objectid = database.find_host(hostname);
482    if (objectid == "")
483    {
484       *Log << "Please define the host " << hostname << " in the database.\n";
485       return;
486    }
487    if (verbose)
488    {
489       *Log << "Object id for " << hostname << " is " << objectid << "\n";
490    }
491
492    pathcontext = xmlXPathNewContext(xmlDom);
493    pathcontext->node = xmlDocGetRootElement(xmlDom);
494    namespaces[0] = pathcontext->node->ns;
495    pathcontext->namespaces = namespaces;
496    pathcontext->nsNr       = 1;
497
498    res = xmlXPathEval((const xmlChar *)"gcmt:data/node()", pathcontext);
499
500    if (res->nodesetval != NULL)
501    {
502       //  Find the first child element of the <data> element.
503
504       xmlNodePtr  node = *res->nodesetval->nodeTab;
505       while (node->type != XML_ELEMENT_NODE)
506       {
507          node = node->next;
508       }
509       if (strcmp((char *)node->name, "log") == 0)
510       {
511          //  Each child contains a log entry, raw or cooked.
512
513          node = node->children;
514          while (node != NULL)
515          {
516             if (node->type == XML_ELEMENT_NODE)
517             {
518                xmlNodePtr  item;
519                String      log_hostname;
520                UTC         log_date;
521                String      raw("");;
522                String      log_service;
523
524                if (strcmp((char *)node->name, "raw") == 0 && node->children != NULL)
525                {
526                   item = node->children;
527                   if (pan.lc == 0)
528                   {
529                      *Log << "Can not cook this type of <raw> log element.\n";
530                   }
531                   else
532                   {
533                      raw = String((const char *)item->content);
534                      if (pan.lc->cook_this(raw, arrival))
535                      {
536                         log_hostname = pan.lc->hostname();
537                         if (log_hostname == "")
538                         {
539                            log_hostname = hostname;
540                         }
541                         log_service = pan.lc->service();
542                         log_date    = pan.lc->timestamp();
543                      }
544                      else
545                      {
546                         *Log << "gcm_input WARNING: Not a valid line: " << raw << "\n";
547                         raw = "";
548                      }
549                   }
550                }
551                else if (strcmp((char *)node->name, "cooked") == 0)
552                {
553                   //  Find the parts of the log entry
554
555                   if (verbose)
556                   {
557                      *Log << "Analyzing cooked element.\n";
558                   }
559                   pathcontext->node = node;
560
561                   res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
562                   if (res->nodesetval != NULL)
563                   {
564                      item = *res->nodesetval->nodeTab;
565                      log_hostname = (const char *)item->content;
566                      if (log_hostname != hostname(0, ~log_hostname))
567                      {
568                         *Log << "Hostname " << log_hostname << " does not match.\n";
569                         log_hostname = "";
570                      }
571                   }
572                   else
573                   {
574                      log_hostname = hostname;
575                   }
576
577                   res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
578                   if (res->nodesetval != NULL)
579                   {
580                      item = *res->nodesetval->nodeTab;
581                      log_service = (const char *)item->content;
582                   }
583                   else
584                   {
585                      log_service = service;
586                   }
587
588                   res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
589                   if (res->nodesetval != NULL)
590                   {
591                      item = *res->nodesetval->nodeTab;
592                      log_date = String((const char *)item->content);
593                   }
594                   else
595                   {
596                      *Log << "<timestamp> missing from cooked log element.\n";
597                   }
598
599                   res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
600                   if (res->nodesetval != NULL)
601                   {
602                      item = *res->nodesetval->nodeTab;
603                      raw = String((const char *)item->content);
604                   }
605                   else
606                   {
607                      *Log << "<raw> missing from cooked log element.\n";
608                   }
609
610                }
611
612                //   Insert a new log record into the database.
613                if (raw != "" && log_hostname != "" && log_date.proper())
614                {
615                   String insertion("insert into log (objectid, servicecode,"
616                         " object_timestamp, timestamp, rawdata, processed) values (");
617
618                   /*   Insert a new record into the log table   */
619
620                   insertion += "'" + objectid + "',";
621                   insertion += "'" + log_service + "',";
622                   insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
623                   insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
624                   insertion += "'" + SQL_Escape(raw) + "',FALSE";
625                   insertion += ")";
626
627                   if (testmode)
628                   {
629                      *Log << insertion << "\n";
630                   }
631                   else
632                   {
633                      database.Query(insertion);
634                   }
635
636                }
637  
638             }
639             node = node->next;
640          }
641       }
642       else if (strcmp((char *)node->name, "parameters") == 0)
643       {
644          //  Each child contains a parameter entry, with at least one property
645
646          String   qry;
647          String   insertion;
648          String   change_notification("");
649          String   out_of_range_notification("");
650          String   create_notification("");
651          String   remove_notification("");
652          bool     initial_entry = false;
653          String   param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
654
655          std::list<param_property>  parameter_template;
656          int                        nr_properties;
657
658          //  Obtain a list of properties from the parameter's class.
659          //  This list is used to create new parameters.
660
661          qry = "select * from parameter_class where name='";
662          qry += param_class + "'";
663          nr_properties = database.Query(qry);
664          for (int i = 0; i < nr_properties; i++)
665          {
666             param_property pp;
667
668             pp.name = database.Field(i, "property_name");
669             pp.type = database.Field(i, "property_type");
670             pp.minimum = database.Field(i, "min");
671             pp.maximum = database.Field(i, "max");
672
673             parameter_template.push_back(pp);
674          }
675
676 #ifdef DEBUG
677          *Log << "Entering a list of " << param_class << " parameters.\n";
678 #endif
679          pathcontext->node = node;
680
681          //  If we don't have any parameters of this class, this will be
682          //  an initial entry.
683
684          qry = "select name from parameter where objectid='";
685          qry += objectid + "' and class='" + param_class + "'";
686          initial_entry = database.Query(qry) == 0;
687
688          node = node->children;
689          while (node != NULL)
690          {
691             if (node->type == XML_ELEMENT_NODE &&
692                 strcmp((char *)node->name, "parameter") == 0)
693             {
694                String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
695
696 #ifdef DEBUG
697                *Log << "Parameter with name " << param_name << "\n";
698 #endif
699                std::list<param_property>  properties;
700                param_property             prop;
701                xmlNodePtr                 item;
702
703                String                     paramid;
704
705
706                //  Collect the parameter's properties.
707
708                item = node->children;
709                while (item != NULL)
710                {
711                   if (item->type == XML_ELEMENT_NODE &&
712                       strcmp((char *)item->name, "property") == 0)
713                   {
714                      prop.name = (const char *)xmlGetProp(item, (const xmlChar *)"name");
715                      if (item->children != NULL)
716                      {
717                         prop.value = (const char *)item->children->content;
718                         properties.push_back(prop);
719                      }
720                      else
721                      {
722                         *Log << "WARNING: Property " << prop.name << " has no value.\n";
723                      }
724                   }
725
726                   //  TODO: Hanlde description element
727
728                   item = item->next;
729                }
730
731                //  Check the parameter in the database.
732
733                std::list<param_property>::iterator pi = properties.begin();
734
735                qry = "select paramid from parameter where objectid='";
736                qry += objectid + "' and class='";
737                qry += param_class + "' and name='";
738                qry += param_name + "'";
739
740                if (database.Query(qry) == 1)
741                {
742                   //  The parameter exists in the database; check all properties.
743
744                   bool  param_changed = false;
745                   bool  out_of_range  = false;
746
747                   paramid = database.Field(0, "paramid");
748                   while (pi != properties.end())
749                   {
750                      qry = "select * from property where paramid='";
751                      qry += paramid + "' and name='";
752                      qry += pi->name + "'";
753                      if (database.Query(qry) == 0)
754                      {
755                         *Log << "Property " << pi->name << " of "
756                              << param_name << " does not exist.\n";
757                         //  Find the property in the template from the class.
758
759                         String property_type("STATIC");
760                         double property_minimum = 0.0;
761                         double property_maximum = 0.0;
762                         std::list<param_property>::iterator ti = parameter_template.begin();
763
764                         while (ti != parameter_template.end() && ti->name != pi->name)
765                         {
766                            ti++;
767                         }
768                         if (ti != parameter_template.end())
769                         {
770                            property_type    = ti->type;
771                            property_minimum = ti->minimum;
772                            property_maximum = ti->maximum;
773                         }
774
775                         insertion = "insert into property (paramid, name, value, type, min, max) values ('";
776                         insertion += paramid + "', '";
777                         insertion += pi->name + "', '";
778                         insertion += pi->value + "', '";
779                         insertion += property_type + "', '";
780                         insertion += String(property_minimum) + "', '";
781                         insertion += String(property_maximum) + "')";
782                         database.Query(insertion);
783
784                         insertion = "insert into history (paramid, modified,";
785                         insertion += " change_nature, changed_property, new_value)";
786                         insertion += " values ('";
787                         insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
788                                              + "', 'CREATED', '";
789                         insertion += pi->name + "', '";
790                         insertion += pi->value + "')";
791                         database.Query(insertion);
792                      }
793                      else
794                      {
795                         param_property  stored_property;
796
797                         stored_property.value    = database.Field(0, "value");
798                         stored_property.type     = database.Field(0, "type");
799                         stored_property.minimum  = database.Field(0, "min");
800                         stored_property.maximum  = database.Field(0, "max");
801
802                         if (stored_property.value != pi->value)
803                         {
804                            *Log << "Property " << pi->name << " of "
805                                 << param_name << " is different.\n";
806
807                            insertion = "update property set value='";
808                            insertion += pi->value + "' where paramid='";
809                            insertion += paramid + "' and name='";
810                            insertion += pi->name + "'";
811
812                            database.Query(insertion);
813
814                            insertion = "insert into history (paramid, modified,";
815                            insertion += " change_nature, changed_property, new_value)";
816                            insertion += " values ('";
817                            insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
818                                                 + "', 'MODIFIED', '";
819                            insertion += pi->name + "', '";
820                            insertion += pi->value + "')";
821
822                            database.Query(insertion);
823                            if (stored_property.type == "DYNAMIC")
824                            {
825                               // Check the value against the range of the property.
826
827                               double numeric_value = pi->value;
828                               if (numeric_value < stored_property.minimum || numeric_value > stored_property.maximum)
829                               {
830                                  out_of_range = true;
831                               }
832                            }
833                            else
834                            {
835                               //  A STATIC property changed.
836                               param_changed = true;
837                            }
838
839                         }
840                      }
841                      pi++;
842                   }
843
844                   if (param_changed)
845                   {
846                      if (change_notification == "")
847                      {
848                         remark = "Gnucomo detected a different property for parameter(s) ";
849                         change_notification = database.new_notification(objectid,
850                                                            "property modified", remark);
851                      }
852
853                      if (change_notification != "")
854                      {
855                         qry = "select * from parameter_notification where notificationid='";
856                         qry += change_notification + "' and paramid='";
857                         qry += paramid + "'";
858
859                         if (database.Query(qry) == 0)
860                         {
861                            insertion = "insert into parameter_notification";
862                            insertion += " (notificationid, paramid) values ('";
863                            insertion += change_notification + "', '";
864                            insertion += paramid + "')";
865
866                            database.Query(insertion);
867                         }
868                      }
869                      else
870                      {
871                         *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
872                      }
873                   }
874
875                   if (out_of_range)
876                   {
877                      if (out_of_range_notification == "")
878                      {
879                         remark = "Gnucomo detected that a property value is out of range.";
880                         out_of_range_notification = database.new_notification(objectid,
881                                                            "property out of range", remark);
882                      }
883
884                      if (out_of_range_notification != "")
885                      {
886                         qry = "select * from parameter_notification where notificationid='";
887                         qry += out_of_range_notification + "' and paramid='";
888                         qry += paramid + "'";
889
890                         if (database.Query(qry) == 0)
891                         {
892                            insertion = "insert into parameter_notification";
893                            insertion += " (notificationid, paramid) values ('";
894                            insertion += out_of_range_notification + "', '";
895                            insertion += paramid + "')";
896
897                            database.Query(insertion);
898                         }
899                      }
900                      else
901                      {
902                         *Log << "gcm_input ERROR: Cannot create 'property out of range' notification.\n";
903                      }
904                   }
905                }
906                else
907                {
908                   //   The parameter does not exist; create anew.
909
910                   // TODO: Insert description
911
912                   insertion = "insert into parameter (objectid, name, class, description) values ('";
913                   insertion += objectid + "', '" + param_name + "', '" + param_class + "', '')";
914                   database.Query(insertion);
915
916                   qry = "select paramid from parameter where objectid='";
917                   qry += objectid + "' and class='";
918                   qry += param_class + "' and name='";
919                   qry += param_name + "'";
920                   database.Query(qry);
921                   paramid = database.Field(0, "paramid");
922
923                   while (pi != properties.end())
924                   {
925                      //  Find the property in the template from the class.
926
927                      String property_type("STATIC");
928                      double property_minimum = 0.0;
929                      double property_maximum = 0.0;
930                      std::list<param_property>::iterator ti = parameter_template.begin();
931
932                      while (ti != parameter_template.end() && ti->name != pi->name)
933                      {
934                         ti++;
935                      }
936                      if (ti != parameter_template.end())
937                      {
938                         property_type    = ti->type;
939                         property_minimum = ti->minimum;
940                         property_maximum = ti->maximum;
941                      }
942
943                      insertion = "insert into property (paramid, name, value, type, min, max) values ('";
944                      insertion += paramid + "', '";
945                      insertion += pi->name + "', '";
946                      insertion += pi->value + "', '";
947                      insertion += property_type + "', '";
948                      insertion += String(property_minimum) + "', '";
949                      insertion += String(property_maximum) + "')";
950                      database.Query(insertion);
951
952                      insertion = "insert into history (paramid, modified,";
953                      insertion += " change_nature, changed_property, new_value)";
954                      insertion += " values ('";
955                      insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
956                                           + "', 'CREATED', '";
957                      insertion += pi->name + "', '";
958                      insertion += pi->value + "')";
959                      database.Query(insertion);
960
961                      pi++;
962                   }
963
964                   if (!initial_entry)
965                   {
966                      if (create_notification == "")
967                      {
968                         remark = "Gnucomo detected new parameter(s) of class " + param_class;
969                         create_notification = database.new_notification(objectid,
970                                                          "parameter created", remark);
971                      }
972                      if (create_notification != "")
973                      {
974                         insertion = "insert into parameter_notification";
975                         insertion += " (notificationid, paramid) values ('";
976                         insertion += create_notification + "', '";
977                         insertion += paramid + "')";
978
979                         database.Query(insertion);
980                      }
981                      else
982                      {
983                         *Log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
984                      }
985                   }
986                }
987             }
988
989             node = node->next;
990          }
991
992          if (!incremental)
993          {
994             //  Check if any parameters in this class have disappeared.
995
996 #ifdef DEBUG
997             *Log << "Checking for disappeared parameters.\n";
998 #endif
999             qry = "select name, paramid from parameter where objectid='";
1000             qry += objectid + "' and class='" + param_class + "'";
1001
1002             int          nr_parameters = database.Query(qry);
1003             pqxx::result parameter_set = database.Result();
1004
1005 #ifdef DEBUG
1006             *Log << nr_parameters << " parameters of class " << param_class << " found in the database.\n";
1007 #endif
1008             for (int i = 0; i < nr_parameters; i++)
1009             {
1010                String XPath;
1011                String param_name, paramid;
1012
1013                param_name = database.Field(parameter_set, i, "name");
1014 #ifdef DEBUG
1015                *Log << "Looking for " << param_name << " in XML tree.\n";
1016 #endif
1017                XPath = "gcmt:parameter[@name='" + param_name + "']";
1018
1019                res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
1020 #ifdef DEBUG
1021                *Log << "XPATH result: " << res->type << ".\n";
1022                *Log << "Nr of nodes found: " << res->nodesetval->nodeNr << ".\n";
1023 #endif
1024                if (res->nodesetval->nodeNr == 0)
1025                {
1026                   // The parameter is in the database but not in the report
1027
1028 #ifdef DEBUG
1029                   *Log << "Could not find " << XPath << " in XML tree.\n";
1030 #endif
1031                   paramid = database.Field(parameter_set, i, "paramid");
1032                   qry ="select change_nature from history where paramid='";
1033                   qry += paramid + "' order by modified desc";
1034                   if (database.Query(qry) <= 0)
1035                   {
1036                      *Log << "Database ERROR: no history record for parameter "
1037                           << param_name << ".\n";
1038                   }
1039                   else if (database.Field(0, "change_nature") != "REMOVED")
1040                   {
1041                      if (verbose)
1042                      {
1043                        *Log << "Removing parameter " << param_name << ".\n";
1044                      }
1045
1046                      insertion = "insert into history (paramid, modified, change_nature)";
1047                      insertion += " values ('";
1048                      insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
1049                                           + "', 'REMOVED')";
1050
1051                      database.Query(insertion);
1052
1053                      if (remove_notification == "")
1054                      {
1055                         remark = "Gnucomo detected that " + param_class
1056                                + " parameters(s) have disappeared ";
1057                         remove_notification = database.new_notification(objectid,
1058                                                          "parameter removed", remark);
1059                      }
1060
1061                      if (remove_notification != "")
1062                      {
1063                         insertion = "insert into parameter_notification";
1064                         insertion += " (notificationid, paramid) values ('";
1065                         insertion += remove_notification + "', '";
1066                         insertion += paramid + "')";
1067
1068                         database.Query(insertion);
1069                      }
1070                      else
1071                      {
1072                         *Log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
1073                      }
1074                   }
1075                }
1076                else
1077                {
1078 #ifdef DEBUG
1079                   *Log << XPath << " was found in XML tree.\n";
1080 #endif
1081                }
1082             }
1083          }
1084       }
1085       else
1086       {
1087          *Log << "Data element " << node->name << " is not supported.\n";
1088       }
1089    }
1090    else
1091    {
1092       *Log << "Data node not found.\n";
1093    }
1094 }
1095
1096 /*=========================================================================
1097 **  NAME           : enter
1098 **  SYNOPSIS       : int enter()
1099 **  PARAMETERS     : 
1100 **  RETURN VALUE   : The number of lines successfully parsed from the input
1101 **
1102 **  DESCRIPTION    : 
1103 **
1104 **  VARS USED      :
1105 **  VARS CHANGED   :
1106 **  FUNCTIONS USED :
1107 **  SEE ALSO       :
1108 **  LAST MODIFIED  : Nov 26, 2003
1109 **=========================================================================
1110 */
1111
1112 int client_message::enter()
1113 {
1114    pan.mf->set_message_type(pan.lc->message_type());
1115
1116    pan.mf->construct_XML(input, xmlBuffer);
1117
1118 #ifdef DEBUG
1119    *Log << "Constructed XML document:\n\n";
1120    *Log << xmlBuffer.str();
1121    *Log << "\n";
1122 #endif
1123
1124    xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
1125
1126    if (xmlDom)
1127    {
1128       if (extractHeader())
1129       {
1130          enterXML();
1131       }
1132    }
1133    else
1134    {
1135       *Log << "XML parser FAILED.\n";
1136    }
1137
1138    return 0;
1139
1140 }