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();
544 if (!log_date.proper())
546 *Log << log_date << " is not a valid timestamp.\n";
551 *Log << "gcm_input WARNING: Not a valid line: " << raw << "\n";
556 else if (strcmp((char *)node->name, "cooked") == 0)
558 // Find the parts of the log entry
562 *Log << "Analyzing cooked element.\n";
564 pathcontext->node = node;
566 res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
567 if (res->nodesetval != NULL)
569 item = *res->nodesetval->nodeTab;
570 log_hostname = (const char *)item->content;
571 if (log_hostname != hostname(0, ~log_hostname))
573 *Log << "Hostname " << log_hostname << " does not match.\n";
579 log_hostname = hostname;
582 res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
583 if (res->nodesetval != NULL)
585 item = *res->nodesetval->nodeTab;
586 log_service = (const char *)item->content;
590 log_service = service;
593 res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
594 if (res->nodesetval != NULL)
596 item = *res->nodesetval->nodeTab;
597 log_date = String((const char *)item->content);
601 *Log << "<timestamp> missing from cooked log element.\n";
604 res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
605 if (res->nodesetval != NULL)
607 item = *res->nodesetval->nodeTab;
608 raw = String((const char *)item->content);
612 *Log << "<raw> missing from cooked log element.\n";
617 // Insert a new log record into the database.
618 if (raw != "" && log_hostname != "" && log_date.proper())
620 String insertion("insert into log (objectid, servicecode,"
621 " object_timestamp, timestamp, rawdata, processed) values (");
623 /* Insert a new record into the log table */
625 insertion += "'" + objectid + "',";
626 insertion += "'" + log_service + "',";
627 insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
628 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
629 insertion += "'" + SQL_Escape(raw) + "',FALSE";
634 *Log << insertion << "\n";
638 database.Query(insertion);
644 *Log << "Can not insert log element " << raw << ".\n";
651 else if (strcmp((char *)node->name, "parameters") == 0)
653 // Each child contains a parameter entry, with at least one property
657 String change_notification("");
658 String out_of_range_notification("");
659 String create_notification("");
660 String remove_notification("");
661 bool initial_entry = false;
662 String param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
664 std::list<param_property> parameter_template;
667 // Obtain a list of properties from the parameter's class.
668 // This list is used to create new parameters.
670 qry = "select * from parameter_class where name='";
671 qry += param_class + "'";
672 nr_properties = database.Query(qry);
673 for (int i = 0; i < nr_properties; i++)
677 pp.name = database.Field(i, "property_name");
678 pp.type = database.Field(i, "property_type");
679 pp.minimum = database.Field(i, "min");
680 pp.maximum = database.Field(i, "max");
682 parameter_template.push_back(pp);
686 *Log << "Entering a list of " << param_class << " parameters.\n";
688 pathcontext->node = node;
690 // If we don't have any parameters of this class, this will be
693 qry = "select name from parameter where objectid='";
694 qry += objectid + "' and class='" + param_class + "'";
695 initial_entry = database.Query(qry) == 0;
697 node = node->children;
700 if (node->type == XML_ELEMENT_NODE &&
701 strcmp((char *)node->name, "parameter") == 0)
703 String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
706 *Log << "Parameter with name " << param_name << "\n";
708 std::list<param_property> properties;
715 // Collect the parameter's properties.
717 item = node->children;
720 if (item->type == XML_ELEMENT_NODE &&
721 strcmp((char *)item->name, "property") == 0)
723 prop.name = (const char *)xmlGetProp(item, (const xmlChar *)"name");
724 if (item->children != NULL)
726 prop.value = (const char *)item->children->content;
727 properties.push_back(prop);
731 *Log << "WARNING: Property " << prop.name << " has no value.\n";
735 // TODO: Hanlde description element
740 // Check the parameter in the database.
742 std::list<param_property>::iterator pi = properties.begin();
744 qry = "select paramid from parameter where objectid='";
745 qry += objectid + "' and class='";
746 qry += param_class + "' and name='";
747 qry += param_name + "'";
749 if (database.Query(qry) == 1)
751 // The parameter exists in the database; check all properties.
753 bool param_changed = false;
754 bool out_of_range = false;
756 paramid = database.Field(0, "paramid");
757 while (pi != properties.end())
759 qry = "select * from property where paramid='";
760 qry += paramid + "' and name='";
761 qry += pi->name + "'";
762 if (database.Query(qry) == 0)
764 *Log << "Property " << pi->name << " of "
765 << param_name << " does not exist.\n";
766 // Find the property in the template from the class.
768 String property_type("STATIC");
769 double property_minimum = 0.0;
770 double property_maximum = 0.0;
771 std::list<param_property>::iterator ti = parameter_template.begin();
773 while (ti != parameter_template.end() && ti->name != pi->name)
777 if (ti != parameter_template.end())
779 property_type = ti->type;
780 property_minimum = ti->minimum;
781 property_maximum = ti->maximum;
784 insertion = "insert into property (paramid, name, value, type, min, max) values ('";
785 insertion += paramid + "', '";
786 insertion += pi->name + "', '";
787 insertion += pi->value + "', '";
788 insertion += property_type + "', '";
789 insertion += String(property_minimum) + "', '";
790 insertion += String(property_maximum) + "')";
791 database.Query(insertion);
793 insertion = "insert into history (paramid, modified,";
794 insertion += " change_nature, changed_property, new_value)";
795 insertion += " values ('";
796 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
798 insertion += pi->name + "', '";
799 insertion += pi->value + "')";
800 database.Query(insertion);
804 param_property stored_property;
806 stored_property.value = database.Field(0, "value");
807 stored_property.type = database.Field(0, "type");
808 stored_property.minimum = database.Field(0, "min");
809 stored_property.maximum = database.Field(0, "max");
811 if (stored_property.value != pi->value)
813 *Log << "Property " << pi->name << " of "
814 << param_name << " is different.\n";
816 insertion = "update property set value='";
817 insertion += pi->value + "' where paramid='";
818 insertion += paramid + "' and name='";
819 insertion += pi->name + "'";
821 database.Query(insertion);
823 insertion = "insert into history (paramid, modified,";
824 insertion += " change_nature, changed_property, new_value)";
825 insertion += " values ('";
826 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
827 + "', 'MODIFIED', '";
828 insertion += pi->name + "', '";
829 insertion += pi->value + "')";
831 database.Query(insertion);
832 if (stored_property.type == "DYNAMIC")
834 // Check the value against the range of the property.
836 double numeric_value = pi->value;
837 if (numeric_value < stored_property.minimum || numeric_value > stored_property.maximum)
844 // A STATIC property changed.
845 param_changed = true;
855 if (change_notification == "")
857 remark = "Gnucomo detected a different property for parameter(s) ";
858 change_notification = database.new_notification(objectid,
859 "property modified", remark);
862 if (change_notification != "")
864 qry = "select * from parameter_notification where notificationid='";
865 qry += change_notification + "' and paramid='";
866 qry += paramid + "'";
868 if (database.Query(qry) == 0)
870 insertion = "insert into parameter_notification";
871 insertion += " (notificationid, paramid) values ('";
872 insertion += change_notification + "', '";
873 insertion += paramid + "')";
875 database.Query(insertion);
880 *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
886 if (out_of_range_notification == "")
888 remark = "Gnucomo detected that a property value is out of range.";
889 out_of_range_notification = database.new_notification(objectid,
890 "property out of range", remark);
893 if (out_of_range_notification != "")
895 qry = "select * from parameter_notification where notificationid='";
896 qry += out_of_range_notification + "' and paramid='";
897 qry += paramid + "'";
899 if (database.Query(qry) == 0)
901 insertion = "insert into parameter_notification";
902 insertion += " (notificationid, paramid) values ('";
903 insertion += out_of_range_notification + "', '";
904 insertion += paramid + "')";
906 database.Query(insertion);
911 *Log << "gcm_input ERROR: Cannot create 'property out of range' notification.\n";
917 // The parameter does not exist; create anew.
919 // TODO: Insert description
921 insertion = "insert into parameter (objectid, name, class, description) values ('";
922 insertion += objectid + "', '" + param_name + "', '" + param_class + "', '')";
923 database.Query(insertion);
925 qry = "select paramid from parameter where objectid='";
926 qry += objectid + "' and class='";
927 qry += param_class + "' and name='";
928 qry += param_name + "'";
930 paramid = database.Field(0, "paramid");
932 while (pi != properties.end())
934 // Find the property in the template from the class.
936 String property_type("STATIC");
937 double property_minimum = 0.0;
938 double property_maximum = 0.0;
939 std::list<param_property>::iterator ti = parameter_template.begin();
941 while (ti != parameter_template.end() && ti->name != pi->name)
945 if (ti != parameter_template.end())
947 property_type = ti->type;
948 property_minimum = ti->minimum;
949 property_maximum = ti->maximum;
952 insertion = "insert into property (paramid, name, value, type, min, max) values ('";
953 insertion += paramid + "', '";
954 insertion += pi->name + "', '";
955 insertion += pi->value + "', '";
956 insertion += property_type + "', '";
957 insertion += String(property_minimum) + "', '";
958 insertion += String(property_maximum) + "')";
959 database.Query(insertion);
961 insertion = "insert into history (paramid, modified,";
962 insertion += " change_nature, changed_property, new_value)";
963 insertion += " values ('";
964 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
966 insertion += pi->name + "', '";
967 insertion += pi->value + "')";
968 database.Query(insertion);
975 if (create_notification == "")
977 remark = "Gnucomo detected new parameter(s) of class " + param_class;
978 create_notification = database.new_notification(objectid,
979 "parameter created", remark);
981 if (create_notification != "")
983 insertion = "insert into parameter_notification";
984 insertion += " (notificationid, paramid) values ('";
985 insertion += create_notification + "', '";
986 insertion += paramid + "')";
988 database.Query(insertion);
992 *Log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
1003 // Check if any parameters in this class have disappeared.
1006 *Log << "Checking for disappeared parameters.\n";
1008 qry = "select name, paramid from parameter where objectid='";
1009 qry += objectid + "' and class='" + param_class + "'";
1011 int nr_parameters = database.Query(qry);
1012 pqxx::result parameter_set = database.Result();
1015 *Log << nr_parameters << " parameters of class " << param_class << " found in the database.\n";
1017 for (int i = 0; i < nr_parameters; i++)
1020 String param_name, paramid;
1022 param_name = database.Field(parameter_set, i, "name");
1024 *Log << "Looking for " << param_name << " in XML tree.\n";
1026 XPath = "gcmt:parameter[@name='" + param_name + "']";
1028 res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
1030 *Log << "XPATH result: " << res->type << ".\n";
1031 *Log << "Nr of nodes found: " << res->nodesetval->nodeNr << ".\n";
1033 if (res->nodesetval->nodeNr == 0)
1035 // The parameter is in the database but not in the report
1038 *Log << "Could not find " << XPath << " in XML tree.\n";
1040 paramid = database.Field(parameter_set, i, "paramid");
1041 qry ="select change_nature from history where paramid='";
1042 qry += paramid + "' order by modified desc";
1043 if (database.Query(qry) <= 0)
1045 *Log << "Database ERROR: no history record for parameter "
1046 << param_name << ".\n";
1048 else if (database.Field(0, "change_nature") != "REMOVED")
1052 *Log << "Removing parameter " << param_name << ".\n";
1055 insertion = "insert into history (paramid, modified, change_nature)";
1056 insertion += " values ('";
1057 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
1060 database.Query(insertion);
1062 if (remove_notification == "")
1064 remark = "Gnucomo detected that " + param_class
1065 + " parameters(s) have disappeared ";
1066 remove_notification = database.new_notification(objectid,
1067 "parameter removed", remark);
1070 if (remove_notification != "")
1072 insertion = "insert into parameter_notification";
1073 insertion += " (notificationid, paramid) values ('";
1074 insertion += remove_notification + "', '";
1075 insertion += paramid + "')";
1077 database.Query(insertion);
1081 *Log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
1088 *Log << XPath << " was found in XML tree.\n";
1096 *Log << "Data element " << node->name << " is not supported.\n";
1101 *Log << "Data node not found.\n";
1105 /*=========================================================================
1107 ** SYNOPSIS : int enter()
1109 ** RETURN VALUE : The number of lines successfully parsed from the input
1117 ** LAST MODIFIED : Nov 26, 2003
1118 **=========================================================================
1121 int client_message::enter()
1123 pan.mf->set_message_type(pan.lc->message_type());
1125 pan.mf->construct_XML(input, xmlBuffer);
1128 *Log << "Constructed XML document:\n\n";
1129 *Log << xmlBuffer.str();
1133 xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
1137 if (extractHeader())
1144 *Log << "XML parser FAILED.\n";