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 *****************************/
123 #include <libxml/xpath.h>
124 #include <libxml/debugXML.h>
130 extern bool verbose; /* Defined in the main application */
131 extern bool testmode;
132 extern bool incremental;
133 extern std::ostream *Log;
135 /* Utility functions */
137 extern String SQL_Escape(String s);
139 /*=========================================================================
140 ** NAME : operator >>
141 ** SYNOPSIS : bool operator >> (message_buffer &, String &)
143 ** RETURN VALUE : True if input was available.
145 ** DESCRIPTION : Input operator. Read the next line from the message.
151 ** LAST MODIFIED : Nov 04, 2002
152 **=========================================================================
155 bool operator >> (message_buffer &b, String &s)
157 bool input_ok = false;
159 if (b.next_line == b.buffer.end())
165 b.buffer.push_back(l);
167 // next_line keeps pointing to the end.
182 /*=========================================================================
183 ** NAME : client_message
184 ** SYNOPSIS : client_message(std::istream *in, gnucomo_database db)
186 ** RETURN VALUE : None
188 ** DESCRIPTION : Client message constructor.
194 ** LAST MODIFIED : Nov 04, 2002
195 **=========================================================================
198 client_message::client_message(std::istream *in, gnucomo_database db)
205 gpg_encrypted = false;
206 classification = UNKNOWN;
211 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}");
213 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
214 static const regex re_dump("^ *DUMP: Date of this level");
215 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
217 static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
218 static const regex re_xml_header("xml .*\?>$");
220 /*=========================================================================
221 ** NAME : extractHeader
222 ** SYNOPSIS : void extractHeader()
224 ** RETURN VALUE : True if the mandatory header elements are available.
226 ** DESCRIPTION : Extract the header information from the XML DOM tree.
232 ** LAST MODIFIED : Nov 26, 2003
233 **=========================================================================
236 bool client_message::extractHeader()
238 xmlNodePtr root, item;
239 xmlNsPtr namespaces[1];
241 xmlXPathObjectPtr res;
242 xmlXPathContextPtr pathcontext;
244 bool header_OK = true;
246 root = xmlDocGetRootElement(xmlDom);
247 namespaces[0] = root->ns;
249 //TODO Ought to check root->name and root->ns->href
251 pathcontext = xmlXPathNewContext(xmlDom);
252 pathcontext->node = xmlDocGetRootElement(xmlDom);
253 pathcontext->namespaces = namespaces;
254 pathcontext->nsNr = 1;
257 xmlDebugDumpNodeList(stdout, pathcontext->node, 0);
260 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:messagetype/text()", pathcontext);
261 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
263 item = *res->nodesetval->nodeTab;
265 // Select a line cooker based on the message type.
268 *Log << "Looking for a line cooker for " << item->content << "\n";
271 std::list<xform>::iterator lci = kitchen.begin();
272 while (pan.lc == 0 && lci != kitchen.end())
274 if (lci->lc->message_type() == (const char *)(item->content))
282 *Log << "Can not find a line cooker for message type " << item->content << "\n";
288 *Log << "Message type not found in XML header.\n";
292 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:hostname/text()", pathcontext);
293 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
295 item = *res->nodesetval->nodeTab;
296 hostname = (const char *)item->content;
300 *Log << "Can not determine the hostname where the message came from.\n";
304 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:service/text()", pathcontext);
305 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
307 item = *res->nodesetval->nodeTab;
308 service = (const char *)item->content;
310 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:time/text()", pathcontext);
311 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
313 item = *res->nodesetval->nodeTab;
314 arrival = String((char *)item->content);
315 if (!arrival.proper())
317 *Log << "Arrival time is not properly stated.\n";
322 //xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
328 /*=========================================================================
330 ** SYNOPSIS : double classify(String host, date arriv_d, hour arriv_t, String serv)
332 ** RETURN VALUE : The certainty with which the message is classified.
340 ** LAST MODIFIED : Nov 27, 2003
341 **=========================================================================
344 double client_message::classify(String host, UTC arriv, String serv)
352 const double epsilon = 0.1; // Threshold for uncertainty
353 const double P = 0.5; // Probability of a wrong match
358 *Log << "Checking for a mail header.\n";
361 /* First, check if the message has a mail header. */
363 if (input >> line && line == re_uxmail_from)
369 /* Skip the mail header until the first empty line. */
371 while (input >> line && line != "")
377 // Push the first line back, we need to read it again.
383 * Now that we have the mail header out of the way, try to figure
384 * out what the content of the message is.
388 *Log << "Classifying message.\n";
394 while (input >> line && uncertainty > epsilon)
398 *Log << " testing: " << line << "\n";
404 gpg_encrypted = true;
405 *Log << "The message is PGP/GnuPG encrypted.\n";
409 // Scan the list of line cookers if there is anything familiar.
411 std::list<xform>::iterator lci = kitchen.begin();
413 while (lci != kitchen.end())
415 if (lci->lc->check_pattern(line))
417 // We have a match; decrease the uncertainty
419 lci->uncertainty *= P;
420 if (uncertainty > lci->uncertainty)
422 uncertainty = lci->uncertainty;
427 *Log << lci->lc->message_type() << " detected with "
428 << lci->uncertainty << " uncertainty.\n";
433 classification = COOKER_OBJECT;
439 certainty = 1.0 - uncertainty;
444 /*=========================================================================
446 ** SYNOPSIS : int enterXML()
448 ** RETURN VALUE : None
450 ** DESCRIPTION : Analyze the DOM tree from the XML input.
451 ** The DOM tree was previously parsed by readXMLinput().
457 ** LAST MODIFIED : Nov 02, 2007
458 **=========================================================================
461 struct param_property
470 void client_message::enterXML()
472 //TODO : return the number of elements that are handled.
474 xmlXPathObjectPtr res;
475 xmlXPathContextPtr pathcontext;
476 xmlNsPtr namespaces[1];
478 /* Try to find the host in the database */
481 String remark; // For notifications
483 objectid = database.find_host(hostname);
486 *Log << "Please define the host " << hostname << " in the database.\n";
491 *Log << "Object id for " << hostname << " is " << objectid << "\n";
494 pathcontext = xmlXPathNewContext(xmlDom);
495 pathcontext->node = xmlDocGetRootElement(xmlDom);
496 namespaces[0] = pathcontext->node->ns;
497 pathcontext->namespaces = namespaces;
498 pathcontext->nsNr = 1;
500 res = xmlXPathEval((const xmlChar *)"gcmt:data/node()", pathcontext);
502 if (res->nodesetval != NULL)
504 // Find the first child element of the <data> element.
506 xmlNodePtr node = *res->nodesetval->nodeTab;
507 while (node->type != XML_ELEMENT_NODE)
511 if (strcmp((char *)node->name, "log") == 0)
513 int log_entry_counter = 0;
515 // Each child contains a log entry, raw or cooked.
517 node = node->children;
520 if (node->type == XML_ELEMENT_NODE)
532 *Log << "Log entry " << log_entry_counter << " is " << node->name << "\n";
534 if (strcmp((char *)node->name, "raw") == 0 && node->children != NULL)
536 item = node->children;
539 *Log << "Can not cook this type of <raw> log element.\n";
543 raw = String((const char *)item->content);
544 if (pan.lc->cook_this(raw, arrival))
546 log_hostname = pan.lc->hostname();
547 if (log_hostname == "")
549 log_hostname = hostname;
551 log_service = pan.lc->service();
552 log_date = pan.lc->timestamp();
554 if (!log_date.proper())
556 *Log << log_date << " is not a valid timestamp.\n";
561 *Log << "gcm_input WARNING: Not a valid line: " << raw << "\n";
566 else if (strcmp((char *)node->name, "cooked") == 0)
568 // Find the parts of the log entry
572 *Log << "Analyzing cooked element.\n";
574 pathcontext->node = node;
576 res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
577 if (res->nodesetval != NULL)
579 item = *res->nodesetval->nodeTab;
580 log_hostname = (const char *)item->content;
581 if (log_hostname != hostname(0, ~log_hostname))
583 *Log << "Hostname " << log_hostname << " does not match.\n";
589 log_hostname = hostname;
592 res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
593 if (res->nodesetval != NULL)
595 item = *res->nodesetval->nodeTab;
596 log_service = (const char *)item->content;
600 log_service = service;
603 res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
604 if (res->nodesetval != NULL)
606 item = *res->nodesetval->nodeTab;
607 log_date = String((const char *)item->content);
611 *Log << "<timestamp> missing from cooked log element nr " << log_entry_counter << ".\n";
614 res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
615 if (res->nodesetval != NULL)
617 item = *res->nodesetval->nodeTab;
618 raw = String((const char *)item->content);
622 *Log << "<raw> missing from cooked log element nr " << log_entry_counter << ".\n";
627 // Insert a new log record into the database.
628 if (raw != "" && log_hostname != "" && log_date.proper())
630 String insertion("insert into log (objectid, servicecode,"
631 " object_timestamp, timestamp, rawdata, processed) values (");
633 /* Insert a new record into the log table */
635 insertion += "'" + objectid + "',";
636 insertion += "'" + log_service + "',";
637 insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
638 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
639 insertion += "'" + SQL_Escape(raw) + "',FALSE";
644 *Log << insertion << "\n";
648 database.Query(insertion);
654 *Log << "Can not insert log element " << raw << ".\n";
661 else if (strcmp((char *)node->name, "parameters") == 0)
663 // Each child contains a parameter entry, with at least one property
667 String change_notification("");
668 String out_of_range_notification("");
669 String create_notification("");
670 String remove_notification("");
671 bool initial_entry = false;
672 String param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
674 std::list<param_property> parameter_template;
677 // Obtain a list of properties from the parameter's class.
678 // This list is used to create new parameters.
680 qry = "select * from parameter_class where name='";
681 qry += param_class + "'";
682 nr_properties = database.Query(qry);
683 for (int i = 0; i < nr_properties; i++)
687 pp.name = database.Field(i, "property_name");
688 pp.type = database.Field(i, "property_type");
689 pp.minimum = database.Field(i, "min");
690 pp.maximum = database.Field(i, "max");
692 parameter_template.push_back(pp);
696 *Log << "Entering a list of " << param_class << " parameters.\n";
698 pathcontext->node = node;
700 // If we don't have any parameters of this class, this will be
703 qry = "select name from parameter where objectid='";
704 qry += objectid + "' and class='" + param_class + "'";
705 initial_entry = database.Query(qry) == 0;
707 node = node->children;
710 if (node->type == XML_ELEMENT_NODE &&
711 strcmp((char *)node->name, "parameter") == 0)
713 String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
716 *Log << "Parameter with name " << param_name << "\n";
718 std::list<param_property> properties;
725 // Collect the parameter's properties.
727 item = node->children;
730 if (item->type == XML_ELEMENT_NODE &&
731 strcmp((char *)item->name, "property") == 0)
733 prop.name = (const char *)xmlGetProp(item, (const xmlChar *)"name");
734 if (item->children != NULL)
736 prop.value = (const char *)item->children->content;
737 properties.push_back(prop);
741 *Log << "WARNING: Property " << prop.name << " has no value.\n";
745 // TODO: Hanlde description element
750 // Check the parameter in the database.
752 std::list<param_property>::iterator pi = properties.begin();
754 qry = "select paramid from parameter where objectid='";
755 qry += objectid + "' and class='";
756 qry += param_class + "' and name='";
757 qry += param_name + "'";
759 if (database.Query(qry) == 1)
761 // The parameter exists in the database; check all properties.
763 bool param_changed = false;
764 bool out_of_range = false;
766 paramid = database.Field(0, "paramid");
767 while (pi != properties.end())
769 qry = "select * from property where paramid='";
770 qry += paramid + "' and name='";
771 qry += pi->name + "'";
772 if (database.Query(qry) == 0)
774 *Log << "Property " << pi->name << " of "
775 << param_name << " does not exist.\n";
776 // Find the property in the template from the class.
778 String property_type("STATIC");
779 double property_minimum = 0.0;
780 double property_maximum = 0.0;
781 std::list<param_property>::iterator ti = parameter_template.begin();
783 while (ti != parameter_template.end() && ti->name != pi->name)
787 if (ti != parameter_template.end())
789 property_type = ti->type;
790 property_minimum = ti->minimum;
791 property_maximum = ti->maximum;
794 insertion = "insert into property (paramid, name, value, type, min, max) values ('";
795 insertion += paramid + "', '";
796 insertion += pi->name + "', '";
797 insertion += pi->value + "', '";
798 insertion += property_type + "', '";
799 insertion += String(property_minimum) + "', '";
800 insertion += String(property_maximum) + "')";
801 database.Query(insertion);
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")
808 insertion += pi->name + "', '";
809 insertion += pi->value + "')";
810 database.Query(insertion);
814 param_property stored_property;
816 stored_property.value = database.Field(0, "value");
817 stored_property.type = database.Field(0, "type");
818 stored_property.minimum = database.Field(0, "min");
819 stored_property.maximum = database.Field(0, "max");
821 if (stored_property.value != pi->value)
823 *Log << "Property " << pi->name << " of "
824 << param_name << " is different.\n";
826 insertion = "update property set value='";
827 insertion += pi->value + "' where paramid='";
828 insertion += paramid + "' and name='";
829 insertion += pi->name + "'";
831 database.Query(insertion);
833 insertion = "insert into history (paramid, modified,";
834 insertion += " change_nature, changed_property, new_value)";
835 insertion += " values ('";
836 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
837 + "', 'MODIFIED', '";
838 insertion += pi->name + "', '";
839 insertion += pi->value + "')";
841 database.Query(insertion);
842 if (stored_property.type == "DYNAMIC")
844 // Check the value against the range of the property.
846 double numeric_value = pi->value;
847 if (numeric_value < stored_property.minimum || numeric_value > stored_property.maximum)
854 // A STATIC property changed.
855 param_changed = true;
865 if (change_notification == "")
867 remark = "Gnucomo detected a different property for parameter(s) ";
868 change_notification = database.new_notification(objectid,
869 "property modified", remark);
872 if (change_notification != "")
874 qry = "select * from parameter_notification where notificationid='";
875 qry += change_notification + "' and paramid='";
876 qry += paramid + "'";
878 if (database.Query(qry) == 0)
880 insertion = "insert into parameter_notification";
881 insertion += " (notificationid, paramid) values ('";
882 insertion += change_notification + "', '";
883 insertion += paramid + "')";
885 database.Query(insertion);
890 *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
896 if (out_of_range_notification == "")
898 remark = "Gnucomo detected that a property value is out of range.";
899 out_of_range_notification = database.new_notification(objectid,
900 "property out of range", remark);
903 if (out_of_range_notification != "")
905 qry = "select * from parameter_notification where notificationid='";
906 qry += out_of_range_notification + "' and paramid='";
907 qry += paramid + "'";
909 if (database.Query(qry) == 0)
911 insertion = "insert into parameter_notification";
912 insertion += " (notificationid, paramid) values ('";
913 insertion += out_of_range_notification + "', '";
914 insertion += paramid + "')";
916 database.Query(insertion);
921 *Log << "gcm_input ERROR: Cannot create 'property out of range' notification.\n";
927 // The parameter does not exist; create anew.
929 // TODO: Insert description
931 insertion = "insert into parameter (objectid, name, class, description) values ('";
932 insertion += objectid + "', '" + param_name + "', '" + param_class + "', '')";
933 database.Query(insertion);
935 qry = "select paramid from parameter where objectid='";
936 qry += objectid + "' and class='";
937 qry += param_class + "' and name='";
938 qry += param_name + "'";
940 paramid = database.Field(0, "paramid");
942 while (pi != properties.end())
944 // Find the property in the template from the class.
946 String property_type("STATIC");
947 double property_minimum = 0.0;
948 double property_maximum = 0.0;
949 std::list<param_property>::iterator ti = parameter_template.begin();
951 while (ti != parameter_template.end() && ti->name != pi->name)
955 if (ti != parameter_template.end())
957 property_type = ti->type;
958 property_minimum = ti->minimum;
959 property_maximum = ti->maximum;
962 insertion = "insert into property (paramid, name, value, type, min, max) values ('";
963 insertion += paramid + "', '";
964 insertion += pi->name + "', '";
965 insertion += pi->value + "', '";
966 insertion += property_type + "', '";
967 insertion += String(property_minimum) + "', '";
968 insertion += String(property_maximum) + "')";
969 database.Query(insertion);
971 insertion = "insert into history (paramid, modified,";
972 insertion += " change_nature, changed_property, new_value)";
973 insertion += " values ('";
974 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
976 insertion += pi->name + "', '";
977 insertion += pi->value + "')";
978 database.Query(insertion);
985 if (create_notification == "")
987 remark = "Gnucomo detected new parameter(s) of class " + param_class;
988 create_notification = database.new_notification(objectid,
989 "parameter created", remark);
991 if (create_notification != "")
993 insertion = "insert into parameter_notification";
994 insertion += " (notificationid, paramid) values ('";
995 insertion += create_notification + "', '";
996 insertion += paramid + "')";
998 database.Query(insertion);
1002 *Log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
1013 // Check if any parameters in this class have disappeared.
1016 *Log << "Checking for disappeared parameters.\n";
1018 qry = "select name, paramid from parameter where objectid='";
1019 qry += objectid + "' and class='" + param_class + "'";
1021 int nr_parameters = database.Query(qry);
1022 pqxx::result parameter_set = database.Result();
1025 *Log << nr_parameters << " parameters of class " << param_class << " found in the database.\n";
1027 for (int i = 0; i < nr_parameters; i++)
1030 String param_name, paramid;
1032 param_name = database.Field(parameter_set, i, "name");
1034 *Log << "Looking for " << param_name << " in XML tree.\n";
1036 XPath = "gcmt:parameter[@name='" + param_name + "']";
1038 res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
1040 *Log << "XPATH result: " << res->type << ".\n";
1041 *Log << "Nr of nodes found: " << res->nodesetval->nodeNr << ".\n";
1043 if (res->nodesetval->nodeNr == 0)
1045 // The parameter is in the database but not in the report
1048 *Log << "Could not find " << XPath << " in XML tree.\n";
1050 paramid = database.Field(parameter_set, i, "paramid");
1051 qry ="select change_nature from history where paramid='";
1052 qry += paramid + "' order by modified desc";
1053 if (database.Query(qry) <= 0)
1055 *Log << "Database ERROR: no history record for parameter "
1056 << param_name << ".\n";
1058 else if (database.Field(0, "change_nature") != "REMOVED")
1062 *Log << "Removing parameter " << param_name << ".\n";
1065 insertion = "insert into history (paramid, modified, change_nature)";
1066 insertion += " values ('";
1067 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
1070 database.Query(insertion);
1072 if (remove_notification == "")
1074 remark = "Gnucomo detected that " + param_class
1075 + " parameters(s) have disappeared ";
1076 remove_notification = database.new_notification(objectid,
1077 "parameter removed", remark);
1080 if (remove_notification != "")
1082 insertion = "insert into parameter_notification";
1083 insertion += " (notificationid, paramid) values ('";
1084 insertion += remove_notification + "', '";
1085 insertion += paramid + "')";
1087 database.Query(insertion);
1091 *Log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
1098 *Log << XPath << " was found in XML tree.\n";
1106 *Log << "Data element " << node->name << " is not supported.\n";
1111 *Log << "Data node not found.\n";
1115 void client_message::saveXML(String filename)
1117 std::ofstream xmlfile;
1119 xmlfile.open(filename);
1120 xmlfile << xmlBuffer.str();
1123 /*=========================================================================
1125 ** SYNOPSIS : int enter()
1127 ** RETURN VALUE : The number of lines successfully parsed from the input
1128 ** or a negative number if an error is encountered.
1129 ** The error return values are:
1130 ** -1 No database connection.
1131 ** -2 XML parser error.
1139 ** LAST MODIFIED : Sep 22, 2020
1140 **=========================================================================
1143 int client_message::enter()
1147 pan.mf->set_message_type(pan.lc->message_type());
1149 pan.mf->construct_XML(input, xmlBuffer);
1152 *Log << "Constructed XML document:\n\n";
1153 *Log << xmlBuffer.str();
1157 xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
1159 if (database.is_connected())
1163 if (extractHeader())
1170 *Log << "XML parser FAILED.\n";
1171 nr_lines = -2; // XML parse error
1176 *Log << "Database connection FAILED.\n";
1177 nr_lines = -1; // No database connection