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 $
13 ** DESCRIPTION : Implementation of the message handling classes
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
25 **************************************************************************/
27 /*****************************
29 Revision 1.19 2011-03-24 10:20:37 arjen
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.
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.
45 Revision 1.17 2005/05/31 05:51:41 arjen
46 Textual changes in parameter notifications
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.
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.
59 Revision 1.14 2003/09/01 06:57:14 arjen
60 Reject log entries that are found to be invalid.
62 Revision 1.13 2003/08/16 15:28:45 arjen
63 Fixed a namespace problem
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.
71 Logrunner is integrated with gcm_input. Although its functionality
72 is still limited, a connection between logrunner and gcm_input
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.
80 Revision 1.10 2003/04/29 09:16:44 arjen
82 Only cooked log entries for now.
84 Revision 1.9 2003/03/29 09:04:10 arjen
85 Extract the hostname out of the 'From:' or 'Message-Id:' line
88 Revision 1.8 2003/03/16 09:42:40 arjen
89 Read IRIX system logs.
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.
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.
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.
108 Revision 1.3 2002/11/09 08:04:27 arjen
109 Added a reference to the GPL
111 Revision 1.2 2002/11/04 10:13:36 arjen
112 Use proper namespace for iostream classes
114 Revision 1.1 2002/10/05 10:25:49 arjen
115 Creation of gcm_input and a first approach to a web interface
117 *****************************/
119 static const char *RCSID = "$Id: message.cpp,v 1.19 2011-03-24 10:20:37 arjen Exp $";
122 #include <libxml/xpath.h>
123 #include <libxml/debugXML.h>
128 extern bool verbose; /* Defined in the main application */
129 extern bool testmode;
130 extern bool incremental;
131 extern std::ostream *Log;
133 /* Utility functions */
135 extern String SQL_Escape(String s);
137 /*=========================================================================
138 ** NAME : operator >>
139 ** SYNOPSIS : bool operator >> (message_buffer &, String &)
141 ** RETURN VALUE : True if input was available.
143 ** DESCRIPTION : Input operator. Read the next line from the message.
149 ** LAST MODIFIED : Nov 04, 2002
150 **=========================================================================
153 bool operator >> (message_buffer &b, String &s)
155 bool input_ok = false;
157 if (b.next_line == b.buffer.end())
163 b.buffer.push_back(l);
165 // next_line keeps pointing to the end.
180 /*=========================================================================
181 ** NAME : client_message
182 ** SYNOPSIS : client_message(std::istream *in, gnucomo_database db)
184 ** RETURN VALUE : None
186 ** DESCRIPTION : Client message constructor.
192 ** LAST MODIFIED : Nov 04, 2002
193 **=========================================================================
196 client_message::client_message(std::istream *in, gnucomo_database db)
203 gpg_encrypted = false;
204 classification = UNKNOWN;
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}");
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:].-]");
215 static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
216 static const regex re_xml_header("xml .*\?>$");
218 /*=========================================================================
219 ** NAME : extractHeader
220 ** SYNOPSIS : void extractHeader()
222 ** RETURN VALUE : True if the mandatory header elements are available.
224 ** DESCRIPTION : Extract the header information from the XML DOM tree.
230 ** LAST MODIFIED : Nov 26, 2003
231 **=========================================================================
234 bool client_message::extractHeader()
236 xmlNodePtr root, item;
237 xmlNsPtr namespaces[1];
239 xmlXPathObjectPtr res;
240 xmlXPathContextPtr pathcontext;
242 bool header_OK = true;
244 root = xmlDocGetRootElement(xmlDom);
245 namespaces[0] = root->ns;
247 //TODO Ought to check root->name and root->ns->href
249 pathcontext = xmlXPathNewContext(xmlDom);
250 pathcontext->node = xmlDocGetRootElement(xmlDom);
251 pathcontext->namespaces = namespaces;
252 pathcontext->nsNr = 1;
255 xmlDebugDumpNodeList(stdout, pathcontext->node, 0);
258 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:messagetype/text()", pathcontext);
259 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
261 item = *res->nodesetval->nodeTab;
263 // Select a line cooker based on the message type.
266 *Log << "Looking for a line cooker for " << item->content << "\n";
269 std::list<xform>::iterator lci = kitchen.begin();
270 while (pan.lc == 0 && lci != kitchen.end())
272 if (lci->lc->message_type() == (const char *)(item->content))
280 *Log << "Can not find a line cooker for message type " << item->content << "\n";
286 *Log << "Message type not found in XML header.\n";
290 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:hostname/text()", pathcontext);
291 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
293 item = *res->nodesetval->nodeTab;
294 hostname = (const char *)item->content;
298 *Log << "Can not determine the hostname where the message came from.\n";
302 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:service/text()", pathcontext);
303 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
305 item = *res->nodesetval->nodeTab;
306 service = (const char *)item->content;
308 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:time/text()", pathcontext);
309 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
311 item = *res->nodesetval->nodeTab;
312 arrival = String((char *)item->content);
313 if (!arrival.proper())
315 *Log << "Arrival time is not properly stated.\n";
320 //xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
326 /*=========================================================================
328 ** SYNOPSIS : double classify(String host, date arriv_d, hour arriv_t, String serv)
330 ** RETURN VALUE : The certainty with which the message is classified.
338 ** LAST MODIFIED : Nov 27, 2003
339 **=========================================================================
342 double client_message::classify(String host, UTC arriv, String serv)
350 const double epsilon = 0.1; // Threshold for uncertainty
351 const double P = 0.5; // Probability of a wrong match
356 *Log << "Checking for a mail header.\n";
359 /* First, check if the message has a mail header. */
361 if (input >> line && line == re_uxmail_from)
367 /* Skip the mail header until the first empty line. */
369 while (input >> line && line != "")
375 // Push the first line back, we need to read it again.
381 * Now that we have the mail header out of the way, try to figure
382 * out what the content of the message is.
386 *Log << "Classifying message.\n";
392 while (input >> line && uncertainty > epsilon)
396 *Log << " testing: " << line << "\n";
402 gpg_encrypted = true;
403 *Log << "The message is PGP/GnuPG encrypted.\n";
407 // Scan the list of line cookers if there is anything familiar.
409 std::list<xform>::iterator lci = kitchen.begin();
411 while (lci != kitchen.end())
413 if (lci->lc->check_pattern(line))
415 // We have a match; decrease the uncertainty
417 lci->uncertainty *= P;
418 if (uncertainty > lci->uncertainty)
420 uncertainty = lci->uncertainty;
425 *Log << lci->lc->message_type() << " detected with "
426 << lci->uncertainty << " uncertainty.\n";
431 classification = COOKER_OBJECT;
437 certainty = 1.0 - uncertainty;
442 /*=========================================================================
444 ** SYNOPSIS : int enterXML()
446 ** RETURN VALUE : None
448 ** DESCRIPTION : Analyze the DOM tree from the XML input.
449 ** The DOM tree was previously parsed by readXMLinput().
455 ** LAST MODIFIED : Nov 02, 2007
456 **=========================================================================
459 struct param_property
468 void client_message::enterXML()
470 //TODO : return the number of elements that are handled.
472 xmlXPathObjectPtr res;
473 xmlXPathContextPtr pathcontext;
474 xmlNsPtr namespaces[1];
476 /* Try to find the host in the database */
479 String remark; // For notifications
481 objectid = database.find_host(hostname);
484 *Log << "Please define the host " << hostname << " in the database.\n";
489 *Log << "Object id for " << hostname << " is " << objectid << "\n";
492 pathcontext = xmlXPathNewContext(xmlDom);
493 pathcontext->node = xmlDocGetRootElement(xmlDom);
494 namespaces[0] = pathcontext->node->ns;
495 pathcontext->namespaces = namespaces;
496 pathcontext->nsNr = 1;
498 res = xmlXPathEval((const xmlChar *)"gcmt:data/node()", pathcontext);
500 if (res->nodesetval != NULL)
502 // Find the first child element of the <data> element.
504 xmlNodePtr node = *res->nodesetval->nodeTab;
505 while (node->type != XML_ELEMENT_NODE)
509 if (strcmp((char *)node->name, "log") == 0)
511 // Each child contains a log entry, raw or cooked.
513 node = node->children;
516 if (node->type == XML_ELEMENT_NODE)
524 if (strcmp((char *)node->name, "raw") == 0 && node->children != NULL)
526 item = node->children;
529 *Log << "Can not cook this type of <raw> log element.\n";
533 raw = String((const char *)item->content);
534 if (pan.lc->cook_this(raw, arrival))
536 log_hostname = pan.lc->hostname();
537 if (log_hostname == "")
539 log_hostname = hostname;
541 log_service = pan.lc->service();
542 log_date = pan.lc->timestamp();
546 *Log << "gcm_input WARNING: Not a valid line: " << raw << "\n";
551 else if (strcmp((char *)node->name, "cooked") == 0)
553 // Find the parts of the log entry
557 *Log << "Analyzing cooked element.\n";
559 pathcontext->node = node;
561 res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
562 if (res->nodesetval != NULL)
564 item = *res->nodesetval->nodeTab;
565 log_hostname = (const char *)item->content;
566 if (log_hostname != hostname(0, ~log_hostname))
568 *Log << "Hostname " << log_hostname << " does not match.\n";
574 log_hostname = hostname;
577 res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
578 if (res->nodesetval != NULL)
580 item = *res->nodesetval->nodeTab;
581 log_service = (const char *)item->content;
585 log_service = service;
588 res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
589 if (res->nodesetval != NULL)
591 item = *res->nodesetval->nodeTab;
592 log_date = String((const char *)item->content);
596 *Log << "<timestamp> missing from cooked log element.\n";
599 res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
600 if (res->nodesetval != NULL)
602 item = *res->nodesetval->nodeTab;
603 raw = String((const char *)item->content);
607 *Log << "<raw> missing from cooked log element.\n";
612 // Insert a new log record into the database.
613 if (raw != "" && log_hostname != "" && log_date.proper())
615 String insertion("insert into log (objectid, servicecode,"
616 " object_timestamp, timestamp, rawdata, processed) values (");
618 /* Insert a new record into the log table */
620 insertion += "'" + objectid + "',";
621 insertion += "'" + log_service + "',";
622 insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
623 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
624 insertion += "'" + SQL_Escape(raw) + "',FALSE";
629 *Log << insertion << "\n";
633 database.Query(insertion);
642 else if (strcmp((char *)node->name, "parameters") == 0)
644 // Each child contains a parameter entry, with at least one property
648 String change_notification("");
649 String out_of_range_notification("");
650 String create_notification("");
651 String remove_notification("");
652 bool initial_entry = false;
653 String param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
655 std::list<param_property> parameter_template;
658 // Obtain a list of properties from the parameter's class.
659 // This list is used to create new parameters.
661 qry = "select * from parameter_class where name='";
662 qry += param_class + "'";
663 nr_properties = database.Query(qry);
664 for (int i = 0; i < nr_properties; i++)
668 pp.name = database.Field(i, "property_name");
669 pp.type = database.Field(i, "property_type");
670 pp.minimum = database.Field(i, "min");
671 pp.maximum = database.Field(i, "max");
673 parameter_template.push_back(pp);
677 *Log << "Entering a list of " << param_class << " parameters.\n";
679 pathcontext->node = node;
681 // If we don't have any parameters of this class, this will be
684 qry = "select name from parameter where objectid='";
685 qry += objectid + "' and class='" + param_class + "'";
686 initial_entry = database.Query(qry) == 0;
688 node = node->children;
691 if (node->type == XML_ELEMENT_NODE &&
692 strcmp((char *)node->name, "parameter") == 0)
694 String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
697 *Log << "Parameter with name " << param_name << "\n";
699 std::list<param_property> properties;
706 // Collect the parameter's properties.
708 item = node->children;
711 if (item->type == XML_ELEMENT_NODE &&
712 strcmp((char *)item->name, "property") == 0)
714 prop.name = (const char *)xmlGetProp(item, (const xmlChar *)"name");
715 if (item->children != NULL)
717 prop.value = (const char *)item->children->content;
718 properties.push_back(prop);
722 *Log << "WARNING: Property " << prop.name << " has no value.\n";
726 // TODO: Hanlde description element
731 // Check the parameter in the database.
733 std::list<param_property>::iterator pi = properties.begin();
735 qry = "select paramid from parameter where objectid='";
736 qry += objectid + "' and class='";
737 qry += param_class + "' and name='";
738 qry += param_name + "'";
740 if (database.Query(qry) == 1)
742 // The parameter exists in the database; check all properties.
744 bool param_changed = false;
745 bool out_of_range = false;
747 paramid = database.Field(0, "paramid");
748 while (pi != properties.end())
750 qry = "select * from property where paramid='";
751 qry += paramid + "' and name='";
752 qry += pi->name + "'";
753 if (database.Query(qry) == 0)
755 *Log << "Property " << pi->name << " of "
756 << param_name << " does not exist.\n";
757 // Find the property in the template from the class.
759 String property_type("STATIC");
760 double property_minimum = 0.0;
761 double property_maximum = 0.0;
762 std::list<param_property>::iterator ti = parameter_template.begin();
764 while (ti != parameter_template.end() && ti->name != pi->name)
768 if (ti != parameter_template.end())
770 property_type = ti->type;
771 property_minimum = ti->minimum;
772 property_maximum = ti->maximum;
775 insertion = "insert into property (paramid, name, value, type, min, max) values ('";
776 insertion += paramid + "', '";
777 insertion += pi->name + "', '";
778 insertion += pi->value + "', '";
779 insertion += property_type + "', '";
780 insertion += String(property_minimum) + "', '";
781 insertion += String(property_maximum) + "')";
782 database.Query(insertion);
784 insertion = "insert into history (paramid, modified,";
785 insertion += " change_nature, changed_property, new_value)";
786 insertion += " values ('";
787 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
789 insertion += pi->name + "', '";
790 insertion += pi->value + "')";
791 database.Query(insertion);
795 param_property stored_property;
797 stored_property.value = database.Field(0, "value");
798 stored_property.type = database.Field(0, "type");
799 stored_property.minimum = database.Field(0, "min");
800 stored_property.maximum = database.Field(0, "max");
802 if (stored_property.value != pi->value)
804 *Log << "Property " << pi->name << " of "
805 << param_name << " is different.\n";
807 insertion = "update property set value='";
808 insertion += pi->value + "' where paramid='";
809 insertion += paramid + "' and name='";
810 insertion += pi->name + "'";
812 database.Query(insertion);
814 insertion = "insert into history (paramid, modified,";
815 insertion += " change_nature, changed_property, new_value)";
816 insertion += " values ('";
817 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
818 + "', 'MODIFIED', '";
819 insertion += pi->name + "', '";
820 insertion += pi->value + "')";
822 database.Query(insertion);
823 if (stored_property.type == "DYNAMIC")
825 // Check the value against the range of the property.
827 double numeric_value = pi->value;
828 if (numeric_value < stored_property.minimum || numeric_value > stored_property.maximum)
835 // A STATIC property changed.
836 param_changed = true;
846 if (change_notification == "")
848 remark = "Gnucomo detected a different property for parameter(s) ";
849 change_notification = database.new_notification(objectid,
850 "property modified", remark);
853 if (change_notification != "")
855 qry = "select * from parameter_notification where notificationid='";
856 qry += change_notification + "' and paramid='";
857 qry += paramid + "'";
859 if (database.Query(qry) == 0)
861 insertion = "insert into parameter_notification";
862 insertion += " (notificationid, paramid) values ('";
863 insertion += change_notification + "', '";
864 insertion += paramid + "')";
866 database.Query(insertion);
871 *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
877 if (out_of_range_notification == "")
879 remark = "Gnucomo detected that a property value is out of range.";
880 out_of_range_notification = database.new_notification(objectid,
881 "property out of range", remark);
884 if (out_of_range_notification != "")
886 qry = "select * from parameter_notification where notificationid='";
887 qry += out_of_range_notification + "' and paramid='";
888 qry += paramid + "'";
890 if (database.Query(qry) == 0)
892 insertion = "insert into parameter_notification";
893 insertion += " (notificationid, paramid) values ('";
894 insertion += out_of_range_notification + "', '";
895 insertion += paramid + "')";
897 database.Query(insertion);
902 *Log << "gcm_input ERROR: Cannot create 'property out of range' notification.\n";
908 // The parameter does not exist; create anew.
910 // TODO: Insert description
912 insertion = "insert into parameter (objectid, name, class, description) values ('";
913 insertion += objectid + "', '" + param_name + "', '" + param_class + "', '')";
914 database.Query(insertion);
916 qry = "select paramid from parameter where objectid='";
917 qry += objectid + "' and class='";
918 qry += param_class + "' and name='";
919 qry += param_name + "'";
921 paramid = database.Field(0, "paramid");
923 while (pi != properties.end())
925 // Find the property in the template from the class.
927 String property_type("STATIC");
928 double property_minimum = 0.0;
929 double property_maximum = 0.0;
930 std::list<param_property>::iterator ti = parameter_template.begin();
932 while (ti != parameter_template.end() && ti->name != pi->name)
936 if (ti != parameter_template.end())
938 property_type = ti->type;
939 property_minimum = ti->minimum;
940 property_maximum = ti->maximum;
943 insertion = "insert into property (paramid, name, value, type, min, max) values ('";
944 insertion += paramid + "', '";
945 insertion += pi->name + "', '";
946 insertion += pi->value + "', '";
947 insertion += property_type + "', '";
948 insertion += String(property_minimum) + "', '";
949 insertion += String(property_maximum) + "')";
950 database.Query(insertion);
952 insertion = "insert into history (paramid, modified,";
953 insertion += " change_nature, changed_property, new_value)";
954 insertion += " values ('";
955 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
957 insertion += pi->name + "', '";
958 insertion += pi->value + "')";
959 database.Query(insertion);
966 if (create_notification == "")
968 remark = "Gnucomo detected new parameter(s) of class " + param_class;
969 create_notification = database.new_notification(objectid,
970 "parameter created", remark);
972 if (create_notification != "")
974 insertion = "insert into parameter_notification";
975 insertion += " (notificationid, paramid) values ('";
976 insertion += create_notification + "', '";
977 insertion += paramid + "')";
979 database.Query(insertion);
983 *Log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
994 // Check if any parameters in this class have disappeared.
997 *Log << "Checking for disappeared parameters.\n";
999 qry = "select name, paramid from parameter where objectid='";
1000 qry += objectid + "' and class='" + param_class + "'";
1002 int nr_parameters = database.Query(qry);
1003 pqxx::result parameter_set = database.Result();
1006 *Log << nr_parameters << " parameters of class " << param_class << " found in the database.\n";
1008 for (int i = 0; i < nr_parameters; i++)
1011 String param_name, paramid;
1013 param_name = database.Field(parameter_set, i, "name");
1015 *Log << "Looking for " << param_name << " in XML tree.\n";
1017 XPath = "gcmt:parameter[@name='" + param_name + "']";
1019 res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
1021 *Log << "XPATH result: " << res->type << ".\n";
1022 *Log << "Nr of nodes found: " << res->nodesetval->nodeNr << ".\n";
1024 if (res->nodesetval->nodeNr == 0)
1026 // The parameter is in the database but not in the report
1029 *Log << "Could not find " << XPath << " in XML tree.\n";
1031 paramid = database.Field(parameter_set, i, "paramid");
1032 qry ="select change_nature from history where paramid='";
1033 qry += paramid + "' order by modified desc";
1034 if (database.Query(qry) <= 0)
1036 *Log << "Database ERROR: no history record for parameter "
1037 << param_name << ".\n";
1039 else if (database.Field(0, "change_nature") != "REMOVED")
1043 *Log << "Removing parameter " << param_name << ".\n";
1046 insertion = "insert into history (paramid, modified, change_nature)";
1047 insertion += " values ('";
1048 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
1051 database.Query(insertion);
1053 if (remove_notification == "")
1055 remark = "Gnucomo detected that " + param_class
1056 + " parameters(s) have disappeared ";
1057 remove_notification = database.new_notification(objectid,
1058 "parameter removed", remark);
1061 if (remove_notification != "")
1063 insertion = "insert into parameter_notification";
1064 insertion += " (notificationid, paramid) values ('";
1065 insertion += remove_notification + "', '";
1066 insertion += paramid + "')";
1068 database.Query(insertion);
1072 *Log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
1079 *Log << XPath << " was found in XML tree.\n";
1087 *Log << "Data element " << node->name << " is not supported.\n";
1092 *Log << "Data node not found.\n";
1096 /*=========================================================================
1098 ** SYNOPSIS : int enter()
1100 ** RETURN VALUE : The number of lines successfully parsed from the input
1108 ** LAST MODIFIED : Nov 26, 2003
1109 **=========================================================================
1112 int client_message::enter()
1114 pan.mf->set_message_type(pan.lc->message_type());
1116 pan.mf->construct_XML(input, xmlBuffer);
1119 *Log << "Constructed XML document:\n\n";
1120 *Log << xmlBuffer.str();
1124 xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
1128 if (extractHeader())
1135 *Log << "XML parser FAILED.\n";