Adjusted date expression for apahce error log
[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                         if (!log_date.proper())
545                         {
546                            *Log << log_date << " is not a valid timestamp.\n";
547                         }
548                      }
549                      else
550                      {
551                         *Log << "gcm_input WARNING: Not a valid line: " << raw << "\n";
552                         raw = "";
553                      }
554                   }
555                }
556                else if (strcmp((char *)node->name, "cooked") == 0)
557                {
558                   //  Find the parts of the log entry
559
560                   if (verbose)
561                   {
562                      *Log << "Analyzing cooked element.\n";
563                   }
564                   pathcontext->node = node;
565
566                   res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
567                   if (res->nodesetval != NULL)
568                   {
569                      item = *res->nodesetval->nodeTab;
570                      log_hostname = (const char *)item->content;
571                      if (log_hostname != hostname(0, ~log_hostname))
572                      {
573                         *Log << "Hostname " << log_hostname << " does not match.\n";
574                         log_hostname = "";
575                      }
576                   }
577                   else
578                   {
579                      log_hostname = hostname;
580                   }
581
582                   res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
583                   if (res->nodesetval != NULL)
584                   {
585                      item = *res->nodesetval->nodeTab;
586                      log_service = (const char *)item->content;
587                   }
588                   else
589                   {
590                      log_service = service;
591                   }
592
593                   res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
594                   if (res->nodesetval != NULL)
595                   {
596                      item = *res->nodesetval->nodeTab;
597                      log_date = String((const char *)item->content);
598                   }
599                   else
600                   {
601                      *Log << "<timestamp> missing from cooked log element.\n";
602                   }
603
604                   res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
605                   if (res->nodesetval != NULL)
606                   {
607                      item = *res->nodesetval->nodeTab;
608                      raw = String((const char *)item->content);
609                   }
610                   else
611                   {
612                      *Log << "<raw> missing from cooked log element.\n";
613                   }
614
615                }
616
617                //   Insert a new log record into the database.
618                if (raw != "" && log_hostname != "" && log_date.proper())
619                {
620                   String insertion("insert into log (objectid, servicecode,"
621                         " object_timestamp, timestamp, rawdata, processed) values (");
622
623                   /*   Insert a new record into the log table   */
624
625                   insertion += "'" + objectid + "',";
626                   insertion += "'" + log_service + "',";
627                   insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
628                   insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
629                   insertion += "'" + SQL_Escape(raw) + "',FALSE";
630                   insertion += ")";
631
632                   if (testmode)
633                   {
634                      *Log << insertion << "\n";
635                   }
636                   else
637                   {
638                      database.Query(insertion);
639                   }
640
641                }
642                else
643                {
644                   *Log << "Can not insert log element " << raw << ".\n";
645                }
646  
647             }
648             node = node->next;
649          }
650       }
651       else if (strcmp((char *)node->name, "parameters") == 0)
652       {
653          //  Each child contains a parameter entry, with at least one property
654
655          String   qry;
656          String   insertion;
657          String   change_notification("");
658          String   out_of_range_notification("");
659          String   create_notification("");
660          String   remove_notification("");
661          bool     initial_entry = false;
662          String   param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
663
664          std::list<param_property>  parameter_template;
665          int                        nr_properties;
666
667          //  Obtain a list of properties from the parameter's class.
668          //  This list is used to create new parameters.
669
670          qry = "select * from parameter_class where name='";
671          qry += param_class + "'";
672          nr_properties = database.Query(qry);
673          for (int i = 0; i < nr_properties; i++)
674          {
675             param_property pp;
676
677             pp.name = database.Field(i, "property_name");
678             pp.type = database.Field(i, "property_type");
679             pp.minimum = database.Field(i, "min");
680             pp.maximum = database.Field(i, "max");
681
682             parameter_template.push_back(pp);
683          }
684
685 #ifdef DEBUG
686          *Log << "Entering a list of " << param_class << " parameters.\n";
687 #endif
688          pathcontext->node = node;
689
690          //  If we don't have any parameters of this class, this will be
691          //  an initial entry.
692
693          qry = "select name from parameter where objectid='";
694          qry += objectid + "' and class='" + param_class + "'";
695          initial_entry = database.Query(qry) == 0;
696
697          node = node->children;
698          while (node != NULL)
699          {
700             if (node->type == XML_ELEMENT_NODE &&
701                 strcmp((char *)node->name, "parameter") == 0)
702             {
703                String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
704
705 #ifdef DEBUG
706                *Log << "Parameter with name " << param_name << "\n";
707 #endif
708                std::list<param_property>  properties;
709                param_property             prop;
710                xmlNodePtr                 item;
711
712                String                     paramid;
713
714
715                //  Collect the parameter's properties.
716
717                item = node->children;
718                while (item != NULL)
719                {
720                   if (item->type == XML_ELEMENT_NODE &&
721                       strcmp((char *)item->name, "property") == 0)
722                   {
723                      prop.name = (const char *)xmlGetProp(item, (const xmlChar *)"name");
724                      if (item->children != NULL)
725                      {
726                         prop.value = (const char *)item->children->content;
727                         properties.push_back(prop);
728                      }
729                      else
730                      {
731                         *Log << "WARNING: Property " << prop.name << " has no value.\n";
732                      }
733                   }
734
735                   //  TODO: Hanlde description element
736
737                   item = item->next;
738                }
739
740                //  Check the parameter in the database.
741
742                std::list<param_property>::iterator pi = properties.begin();
743
744                qry = "select paramid from parameter where objectid='";
745                qry += objectid + "' and class='";
746                qry += param_class + "' and name='";
747                qry += param_name + "'";
748
749                if (database.Query(qry) == 1)
750                {
751                   //  The parameter exists in the database; check all properties.
752
753                   bool  param_changed = false;
754                   bool  out_of_range  = false;
755
756                   paramid = database.Field(0, "paramid");
757                   while (pi != properties.end())
758                   {
759                      qry = "select * from property where paramid='";
760                      qry += paramid + "' and name='";
761                      qry += pi->name + "'";
762                      if (database.Query(qry) == 0)
763                      {
764                         *Log << "Property " << pi->name << " of "
765                              << param_name << " does not exist.\n";
766                         //  Find the property in the template from the class.
767
768                         String property_type("STATIC");
769                         double property_minimum = 0.0;
770                         double property_maximum = 0.0;
771                         std::list<param_property>::iterator ti = parameter_template.begin();
772
773                         while (ti != parameter_template.end() && ti->name != pi->name)
774                         {
775                            ti++;
776                         }
777                         if (ti != parameter_template.end())
778                         {
779                            property_type    = ti->type;
780                            property_minimum = ti->minimum;
781                            property_maximum = ti->maximum;
782                         }
783
784                         insertion = "insert into property (paramid, name, value, type, min, max) values ('";
785                         insertion += paramid + "', '";
786                         insertion += pi->name + "', '";
787                         insertion += pi->value + "', '";
788                         insertion += property_type + "', '";
789                         insertion += String(property_minimum) + "', '";
790                         insertion += String(property_maximum) + "')";
791                         database.Query(insertion);
792
793                         insertion = "insert into history (paramid, modified,";
794                         insertion += " change_nature, changed_property, new_value)";
795                         insertion += " values ('";
796                         insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
797                                              + "', 'CREATED', '";
798                         insertion += pi->name + "', '";
799                         insertion += pi->value + "')";
800                         database.Query(insertion);
801                      }
802                      else
803                      {
804                         param_property  stored_property;
805
806                         stored_property.value    = database.Field(0, "value");
807                         stored_property.type     = database.Field(0, "type");
808                         stored_property.minimum  = database.Field(0, "min");
809                         stored_property.maximum  = database.Field(0, "max");
810
811                         if (stored_property.value != pi->value)
812                         {
813                            *Log << "Property " << pi->name << " of "
814                                 << param_name << " is different.\n";
815
816                            insertion = "update property set value='";
817                            insertion += pi->value + "' where paramid='";
818                            insertion += paramid + "' and name='";
819                            insertion += pi->name + "'";
820
821                            database.Query(insertion);
822
823                            insertion = "insert into history (paramid, modified,";
824                            insertion += " change_nature, changed_property, new_value)";
825                            insertion += " values ('";
826                            insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
827                                                 + "', 'MODIFIED', '";
828                            insertion += pi->name + "', '";
829                            insertion += pi->value + "')";
830
831                            database.Query(insertion);
832                            if (stored_property.type == "DYNAMIC")
833                            {
834                               // Check the value against the range of the property.
835
836                               double numeric_value = pi->value;
837                               if (numeric_value < stored_property.minimum || numeric_value > stored_property.maximum)
838                               {
839                                  out_of_range = true;
840                               }
841                            }
842                            else
843                            {
844                               //  A STATIC property changed.
845                               param_changed = true;
846                            }
847
848                         }
849                      }
850                      pi++;
851                   }
852
853                   if (param_changed)
854                   {
855                      if (change_notification == "")
856                      {
857                         remark = "Gnucomo detected a different property for parameter(s) ";
858                         change_notification = database.new_notification(objectid,
859                                                            "property modified", remark);
860                      }
861
862                      if (change_notification != "")
863                      {
864                         qry = "select * from parameter_notification where notificationid='";
865                         qry += change_notification + "' and paramid='";
866                         qry += paramid + "'";
867
868                         if (database.Query(qry) == 0)
869                         {
870                            insertion = "insert into parameter_notification";
871                            insertion += " (notificationid, paramid) values ('";
872                            insertion += change_notification + "', '";
873                            insertion += paramid + "')";
874
875                            database.Query(insertion);
876                         }
877                      }
878                      else
879                      {
880                         *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
881                      }
882                   }
883
884                   if (out_of_range)
885                   {
886                      if (out_of_range_notification == "")
887                      {
888                         remark = "Gnucomo detected that a property value is out of range.";
889                         out_of_range_notification = database.new_notification(objectid,
890                                                            "property out of range", remark);
891                      }
892
893                      if (out_of_range_notification != "")
894                      {
895                         qry = "select * from parameter_notification where notificationid='";
896                         qry += out_of_range_notification + "' and paramid='";
897                         qry += paramid + "'";
898
899                         if (database.Query(qry) == 0)
900                         {
901                            insertion = "insert into parameter_notification";
902                            insertion += " (notificationid, paramid) values ('";
903                            insertion += out_of_range_notification + "', '";
904                            insertion += paramid + "')";
905
906                            database.Query(insertion);
907                         }
908                      }
909                      else
910                      {
911                         *Log << "gcm_input ERROR: Cannot create 'property out of range' notification.\n";
912                      }
913                   }
914                }
915                else
916                {
917                   //   The parameter does not exist; create anew.
918
919                   // TODO: Insert description
920
921                   insertion = "insert into parameter (objectid, name, class, description) values ('";
922                   insertion += objectid + "', '" + param_name + "', '" + param_class + "', '')";
923                   database.Query(insertion);
924
925                   qry = "select paramid from parameter where objectid='";
926                   qry += objectid + "' and class='";
927                   qry += param_class + "' and name='";
928                   qry += param_name + "'";
929                   database.Query(qry);
930                   paramid = database.Field(0, "paramid");
931
932                   while (pi != properties.end())
933                   {
934                      //  Find the property in the template from the class.
935
936                      String property_type("STATIC");
937                      double property_minimum = 0.0;
938                      double property_maximum = 0.0;
939                      std::list<param_property>::iterator ti = parameter_template.begin();
940
941                      while (ti != parameter_template.end() && ti->name != pi->name)
942                      {
943                         ti++;
944                      }
945                      if (ti != parameter_template.end())
946                      {
947                         property_type    = ti->type;
948                         property_minimum = ti->minimum;
949                         property_maximum = ti->maximum;
950                      }
951
952                      insertion = "insert into property (paramid, name, value, type, min, max) values ('";
953                      insertion += paramid + "', '";
954                      insertion += pi->name + "', '";
955                      insertion += pi->value + "', '";
956                      insertion += property_type + "', '";
957                      insertion += String(property_minimum) + "', '";
958                      insertion += String(property_maximum) + "')";
959                      database.Query(insertion);
960
961                      insertion = "insert into history (paramid, modified,";
962                      insertion += " change_nature, changed_property, new_value)";
963                      insertion += " values ('";
964                      insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
965                                           + "', 'CREATED', '";
966                      insertion += pi->name + "', '";
967                      insertion += pi->value + "')";
968                      database.Query(insertion);
969
970                      pi++;
971                   }
972
973                   if (!initial_entry)
974                   {
975                      if (create_notification == "")
976                      {
977                         remark = "Gnucomo detected new parameter(s) of class " + param_class;
978                         create_notification = database.new_notification(objectid,
979                                                          "parameter created", remark);
980                      }
981                      if (create_notification != "")
982                      {
983                         insertion = "insert into parameter_notification";
984                         insertion += " (notificationid, paramid) values ('";
985                         insertion += create_notification + "', '";
986                         insertion += paramid + "')";
987
988                         database.Query(insertion);
989                      }
990                      else
991                      {
992                         *Log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
993                      }
994                   }
995                }
996             }
997
998             node = node->next;
999          }
1000
1001          if (!incremental)
1002          {
1003             //  Check if any parameters in this class have disappeared.
1004
1005 #ifdef DEBUG
1006             *Log << "Checking for disappeared parameters.\n";
1007 #endif
1008             qry = "select name, paramid from parameter where objectid='";
1009             qry += objectid + "' and class='" + param_class + "'";
1010
1011             int          nr_parameters = database.Query(qry);
1012             pqxx::result parameter_set = database.Result();
1013
1014 #ifdef DEBUG
1015             *Log << nr_parameters << " parameters of class " << param_class << " found in the database.\n";
1016 #endif
1017             for (int i = 0; i < nr_parameters; i++)
1018             {
1019                String XPath;
1020                String param_name, paramid;
1021
1022                param_name = database.Field(parameter_set, i, "name");
1023 #ifdef DEBUG
1024                *Log << "Looking for " << param_name << " in XML tree.\n";
1025 #endif
1026                XPath = "gcmt:parameter[@name='" + param_name + "']";
1027
1028                res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
1029 #ifdef DEBUG
1030                *Log << "XPATH result: " << res->type << ".\n";
1031                *Log << "Nr of nodes found: " << res->nodesetval->nodeNr << ".\n";
1032 #endif
1033                if (res->nodesetval->nodeNr == 0)
1034                {
1035                   // The parameter is in the database but not in the report
1036
1037 #ifdef DEBUG
1038                   *Log << "Could not find " << XPath << " in XML tree.\n";
1039 #endif
1040                   paramid = database.Field(parameter_set, i, "paramid");
1041                   qry ="select change_nature from history where paramid='";
1042                   qry += paramid + "' order by modified desc";
1043                   if (database.Query(qry) <= 0)
1044                   {
1045                      *Log << "Database ERROR: no history record for parameter "
1046                           << param_name << ".\n";
1047                   }
1048                   else if (database.Field(0, "change_nature") != "REMOVED")
1049                   {
1050                      if (verbose)
1051                      {
1052                        *Log << "Removing parameter " << param_name << ".\n";
1053                      }
1054
1055                      insertion = "insert into history (paramid, modified, change_nature)";
1056                      insertion += " values ('";
1057                      insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
1058                                           + "', 'REMOVED')";
1059
1060                      database.Query(insertion);
1061
1062                      if (remove_notification == "")
1063                      {
1064                         remark = "Gnucomo detected that " + param_class
1065                                + " parameters(s) have disappeared ";
1066                         remove_notification = database.new_notification(objectid,
1067                                                          "parameter removed", remark);
1068                      }
1069
1070                      if (remove_notification != "")
1071                      {
1072                         insertion = "insert into parameter_notification";
1073                         insertion += " (notificationid, paramid) values ('";
1074                         insertion += remove_notification + "', '";
1075                         insertion += paramid + "')";
1076
1077                         database.Query(insertion);
1078                      }
1079                      else
1080                      {
1081                         *Log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
1082                      }
1083                   }
1084                }
1085                else
1086                {
1087 #ifdef DEBUG
1088                   *Log << XPath << " was found in XML tree.\n";
1089 #endif
1090                }
1091             }
1092          }
1093       }
1094       else
1095       {
1096          *Log << "Data element " << node->name << " is not supported.\n";
1097       }
1098    }
1099    else
1100    {
1101       *Log << "Data node not found.\n";
1102    }
1103 }
1104
1105 /*=========================================================================
1106 **  NAME           : enter
1107 **  SYNOPSIS       : int enter()
1108 **  PARAMETERS     : 
1109 **  RETURN VALUE   : The number of lines successfully parsed from the input
1110 **
1111 **  DESCRIPTION    : 
1112 **
1113 **  VARS USED      :
1114 **  VARS CHANGED   :
1115 **  FUNCTIONS USED :
1116 **  SEE ALSO       :
1117 **  LAST MODIFIED  : Nov 26, 2003
1118 **=========================================================================
1119 */
1120
1121 int client_message::enter()
1122 {
1123    pan.mf->set_message_type(pan.lc->message_type());
1124
1125    pan.mf->construct_XML(input, xmlBuffer);
1126
1127 #ifdef DEBUG
1128    *Log << "Constructed XML document:\n\n";
1129    *Log << xmlBuffer.str();
1130    *Log << "\n";
1131 #endif
1132
1133    xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
1134
1135    if (xmlDom)
1136    {
1137       if (extractHeader())
1138       {
1139          enterXML();
1140       }
1141    }
1142    else
1143    {
1144       *Log << "XML parser FAILED.\n";
1145    }
1146
1147    return 0;
1148
1149 }