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