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