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