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.18 $
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.18 2007-11-03 10:23:53 arjen
30 Handling of parameters is greatly improved.
31 When creating a new parameter from an XML report which is fed into
32 gcm_input, the class definition is used as a template to fill in
33 the default values for the properties.
34 The class is also used as a template when a new property is added
35 to an existing parameter.
37 DYNAMIC properties are now handled properly. Instead of making a
38 'changed property' notification, the value is checked against the
39 defined range for the property. An 'out of range' notification
40 is created when this condition is detected.
42 Revision 1.17 2005/05/31 05:51:41 arjen
43 Textual changes in parameter notifications
45 Revision 1.16 2003/12/04 10:38:09 arjen
46 Major redesign. All input is handled through XML. Raw input data is first
47 transformed into an XML document for further processing.
48 A collection of polymorphic classes handle the transformation of various
49 input formats into XML.
50 Classifying input data is done with a finite improbability calculation.
52 Revision 1.15 2003/10/27 11:28:27 arjen
53 Do not add another parameter_notification record is the notification
54 already exists for that parameter.
56 Revision 1.14 2003/09/01 06:57:14 arjen
57 Reject log entries that are found to be invalid.
59 Revision 1.13 2003/08/16 15:28:45 arjen
60 Fixed a namespace problem
62 Revision 1.12 2003/08/11 16:56:16 arjen
63 Different kinds of log files are parsed by a collection of objects
64 of different classes, derived from the base class line_cooker
65 Depending on the message content or the message_type element in
66 XML, one of these objects is selected.
68 Logrunner is integrated with gcm_input. Although its functionality
69 is still limited, a connection between logrunner and gcm_input
72 Revision 1.11 2003/08/05 08:15:00 arjen
73 Debug output to the log stream instead of cerr.
74 Fixed namespace problems in XPath searches of the DOM.
75 Moved string utility functions to a separate file.
77 Revision 1.10 2003/04/29 09:16:44 arjen
79 Only cooked log entries for now.
81 Revision 1.9 2003/03/29 09:04:10 arjen
82 Extract the hostname out of the 'From:' or 'Message-Id:' line
85 Revision 1.8 2003/03/16 09:42:40 arjen
86 Read IRIX system logs.
88 Revision 1.7 2003/02/21 08:08:05 arjen
89 Gcm_input also detects packages that are removed from the system.
90 Determining the version number of a package in a RPM
91 list is improved. Only the last one or two parts of the string that
92 begin with a '-' and a number are considered the version.
94 Revision 1.6 2003/02/05 09:37:51 arjen
95 Create notifications when a new package is discovered
96 in a 'rpm -qa' list or when the version of a package is changed.
98 Revision 1.4 2002/12/06 22:26:28 arjen
99 Set the value of log.processed to FALSE when inserting a
100 new log entry into the database
101 When a syslog entry arrives from last year, gcm_input subtracts one from the
102 year of arrival to create the year of the log entry.
103 Read output from "rpm -qa" and enter packages in the parameter table.
105 Revision 1.3 2002/11/09 08:04:27 arjen
106 Added a reference to the GPL
108 Revision 1.2 2002/11/04 10:13:36 arjen
109 Use proper namespace for iostream classes
111 Revision 1.1 2002/10/05 10:25:49 arjen
112 Creation of gcm_input and a first approach to a web interface
114 *****************************/
116 static const char *RCSID = "$Id: message.cpp,v 1.18 2007-11-03 10:23:53 arjen Exp $";
119 #include <libxml/xpath.h>
120 #include <libxml/debugXML.h>
125 extern bool verbose; /* Defined in the main application */
126 extern bool testmode;
127 extern bool incremental;
128 extern std::ostream *Log;
130 /* Utility functions */
132 extern String SQL_Escape(String s);
134 /*=========================================================================
135 ** NAME : operator >>
136 ** SYNOPSIS : bool operator >> (message_buffer &, String &)
138 ** RETURN VALUE : True if input was available.
140 ** DESCRIPTION : Input operator. Read the next line from the message.
146 ** LAST MODIFIED : Nov 04, 2002
147 **=========================================================================
150 bool operator >> (message_buffer &b, String &s)
152 bool input_ok = false;
154 if (b.next_line == b.buffer.end())
160 b.buffer.push_back(l);
162 // next_line keeps pointing to the end.
177 /*=========================================================================
178 ** NAME : client_message
179 ** SYNOPSIS : client_message(std::istream *in, gnucomo_database db)
181 ** RETURN VALUE : None
183 ** DESCRIPTION : Client message constructor.
189 ** LAST MODIFIED : Nov 04, 2002
190 **=========================================================================
193 client_message::client_message(std::istream *in, gnucomo_database db)
200 gpg_encrypted = false;
201 classification = UNKNOWN;
206 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}");
208 static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
209 static const regex re_dump("^ *DUMP: Date of this level");
210 static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
212 static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
213 static const regex re_xml_header("xml .*\?>$");
215 /*=========================================================================
216 ** NAME : extractHeader
217 ** SYNOPSIS : void extractHeader()
219 ** RETURN VALUE : True if the mandatory header elements are available.
221 ** DESCRIPTION : Extract the header information from the XML DOM tree.
227 ** LAST MODIFIED : Nov 26, 2003
228 **=========================================================================
231 bool client_message::extractHeader()
233 xmlNodePtr root, item;
234 xmlNsPtr namespaces[1];
236 xmlXPathObjectPtr res;
237 xmlXPathContextPtr pathcontext;
239 bool header_OK = true;
241 root = xmlDocGetRootElement(xmlDom);
242 namespaces[0] = root->ns;
244 //TODO Ought to check root->name and root->ns->href
246 pathcontext = xmlXPathNewContext(xmlDom);
247 pathcontext->node = xmlDocGetRootElement(xmlDom);
248 pathcontext->namespaces = namespaces;
249 pathcontext->nsNr = 1;
252 xmlDebugDumpNodeList(stdout, pathcontext->node, 0);
255 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:messagetype/text()", pathcontext);
256 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
258 item = *res->nodesetval->nodeTab;
260 // Select a line cooker based on the message type.
263 *Log << "Looking for a line cooker for " << item->content << "\n";
266 std::list<xform>::iterator lci = kitchen.begin();
267 while (pan.lc == 0 && lci != kitchen.end())
269 if (lci->lc->message_type() == (const char *)(item->content))
277 *Log << "Can not find a line cooker for message type " << item->content << "\n";
283 *Log << "Message type not found in XML header.\n";
287 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:hostname/text()", pathcontext);
288 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
290 item = *res->nodesetval->nodeTab;
291 hostname = (const char *)item->content;
295 *Log << "Can not determine the hostname where the message came from.\n";
299 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:service/text()", pathcontext);
300 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
302 item = *res->nodesetval->nodeTab;
303 service = (const char *)item->content;
305 res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:time/text()", pathcontext);
306 if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
308 item = *res->nodesetval->nodeTab;
309 arrival = String((char *)item->content);
310 if (!arrival.proper())
312 *Log << "Arrival time is not properly stated.\n";
317 //xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
323 /*=========================================================================
325 ** SYNOPSIS : double classify(String host, date arriv_d, hour arriv_t, String serv)
327 ** RETURN VALUE : The certainty with which the message is classified.
335 ** LAST MODIFIED : Nov 27, 2003
336 **=========================================================================
339 double client_message::classify(String host, UTC arriv, String serv)
347 const double epsilon = 0.1; // Threshold for uncertainty
348 const double P = 0.5; // Probability of a wrong match
353 *Log << "Checking for a mail header.\n";
356 /* First, check if the message has a mail header. */
358 if (input >> line && line == re_uxmail_from)
364 /* Skip the mail header until the first empty line. */
366 while (input >> line && line != "")
372 // Push the first line back, we need to read it again.
378 * Now that we have the mail header out of the way, try to figure
379 * out what the content of the message is.
383 *Log << "Classifying message.\n";
389 while (input >> line && uncertainty > epsilon)
393 *Log << " testing: " << line << "\n";
399 gpg_encrypted = true;
400 *Log << "The message is PGP/GnuPG encrypted.\n";
404 // Scan the list of line cookers if there is anything familiar.
406 std::list<xform>::iterator lci = kitchen.begin();
408 while (lci != kitchen.end())
410 if (lci->lc->check_pattern(line))
412 // We have a match; decrease the uncertainty
414 lci->uncertainty *= P;
415 if (uncertainty > lci->uncertainty)
417 uncertainty = lci->uncertainty;
422 *Log << lci->lc->message_type() << " detected with "
423 << lci->uncertainty << " uncertainty.\n";
428 classification = COOKER_OBJECT;
434 certainty = 1.0 - uncertainty;
439 /*=========================================================================
441 ** SYNOPSIS : int enterXML()
443 ** RETURN VALUE : None
445 ** DESCRIPTION : Analyze the DOM tree from the XML input.
446 ** The DOM tree was previously parsed by readXMLinput().
452 ** LAST MODIFIED : Nov 02, 2007
453 **=========================================================================
456 struct param_property
465 void client_message::enterXML()
467 //TODO : return the number of elements that are handled.
469 xmlXPathObjectPtr res;
470 xmlXPathContextPtr pathcontext;
471 xmlNsPtr namespaces[1];
473 /* Try to find the host in the database */
476 String remark; // For notifications
478 objectid = database.find_host(hostname);
481 *Log << "Please define the host " << hostname << " in the database.\n";
486 *Log << "Object id for " << hostname << " is " << objectid << "\n";
489 pathcontext = xmlXPathNewContext(xmlDom);
490 pathcontext->node = xmlDocGetRootElement(xmlDom);
491 namespaces[0] = pathcontext->node->ns;
492 pathcontext->namespaces = namespaces;
493 pathcontext->nsNr = 1;
495 res = xmlXPathEval((const xmlChar *)"gcmt:data/node()", pathcontext);
497 if (res->nodesetval != NULL)
499 // Find the first child element of the <data> element.
501 xmlNodePtr node = *res->nodesetval->nodeTab;
502 while (node->type != XML_ELEMENT_NODE)
506 if (strcmp((char *)node->name, "log") == 0)
508 // Each child contains a log entry, raw or cooked.
510 node = node->children;
513 if (node->type == XML_ELEMENT_NODE)
521 if (strcmp((char *)node->name, "raw") == 0 && node->children != NULL)
523 item = node->children;
526 *Log << "Can not cook this type of <raw> log element.\n";
530 raw = String((const char *)item->content);
531 if (pan.lc->cook_this(raw, arrival))
533 log_hostname = pan.lc->hostname();
534 if (log_hostname == "")
536 log_hostname = hostname;
538 log_service = pan.lc->service();
539 log_date = pan.lc->timestamp();
543 *Log << "gcm_input WARNING: Not a valid line: " << raw << "\n";
548 else if (strcmp((char *)node->name, "cooked") == 0)
550 // Find the parts of the log entry
554 *Log << "Analyzing cooked element.\n";
556 pathcontext->node = node;
558 res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
559 if (res->nodesetval != NULL)
561 item = *res->nodesetval->nodeTab;
562 log_hostname = (const char *)item->content;
563 if (log_hostname != hostname(0, ~log_hostname))
565 *Log << "Hostname " << log_hostname << " does not match.\n";
571 log_hostname = hostname;
574 res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
575 if (res->nodesetval != NULL)
577 item = *res->nodesetval->nodeTab;
578 log_service = (const char *)item->content;
582 log_service = service;
585 res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
586 if (res->nodesetval != NULL)
588 item = *res->nodesetval->nodeTab;
589 log_date = String((const char *)item->content);
593 *Log << "<timestamp> missing from cooked log element.\n";
596 res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
597 if (res->nodesetval != NULL)
599 item = *res->nodesetval->nodeTab;
600 raw = String((const char *)item->content);
604 *Log << "<raw> missing from cooked log element.\n";
609 // Insert a new log record into the database.
610 if (raw != "" && log_hostname != "" && log_date.proper())
612 String insertion("insert into log (objectid, servicecode,"
613 " object_timestamp, timestamp, rawdata, processed) values (");
615 /* Insert a new record into the log table */
617 insertion += "'" + objectid + "',";
618 insertion += "'" + log_service + "',";
619 insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
620 insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
621 insertion += "'" + SQL_Escape(raw) + "',FALSE";
626 *Log << insertion << "\n";
630 database.Query(insertion);
639 else if (strcmp((char *)node->name, "parameters") == 0)
641 // Each child contains a parameter entry, with at least one property
645 String change_notification("");
646 String out_of_range_notification("");
647 String create_notification("");
648 String remove_notification("");
649 bool initial_entry = false;
650 String param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
652 std::list<param_property> parameter_template;
655 // Obtain a list of properties from the parameter's class.
656 // This list is used to create new parameters.
658 qry = "select * from parameter_class where name='";
659 qry += param_class + "'";
660 nr_properties = database.Query(qry);
661 for (int i = 0; i < nr_properties; i++)
665 pp.name = database.Field(i, "property_name");
666 pp.type = database.Field(i, "property_type");
667 pp.minimum = database.Field(i, "min");
668 pp.maximum = database.Field(i, "max");
670 parameter_template.push_back(pp);
674 *Log << "Entering a list of " << param_class << " parameters.\n";
676 pathcontext->node = node;
678 // If we don't have any parameters of this class, this will be
681 qry = "select name from parameter where objectid='";
682 qry += objectid + "' and class='" + param_class + "'";
683 initial_entry = database.Query(qry) == 0;
685 node = node->children;
688 if (node->type == XML_ELEMENT_NODE &&
689 strcmp((char *)node->name, "parameter") == 0)
691 String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
694 *Log << "Parameter with name " << param_name << "\n";
696 std::list<param_property> properties;
703 // Collect the parameter's properties.
705 item = node->children;
708 if (item->type == XML_ELEMENT_NODE &&
709 strcmp((char *)item->name, "property") == 0)
711 prop.name = (const char *)xmlGetProp(item, (const xmlChar *)"name");
712 if (item->children != NULL)
714 prop.value = (const char *)item->children->content;
715 properties.push_back(prop);
719 *Log << "WARNING: Property " << prop.name << " has no value.\n";
723 // TODO: Hanlde description element
728 // Check the parameter in the database.
730 std::list<param_property>::iterator pi = properties.begin();
732 qry = "select paramid from parameter where objectid='";
733 qry += objectid + "' and class='";
734 qry += param_class + "' and name='";
735 qry += param_name + "'";
737 if (database.Query(qry) == 1)
739 // The parameter exists in the database; check all properties.
741 bool param_changed = false;
742 bool out_of_range = false;
744 paramid = database.Field(0, "paramid");
745 while (pi != properties.end())
747 qry = "select * from property where paramid='";
748 qry += paramid + "' and name='";
749 qry += pi->name + "'";
750 if (database.Query(qry) == 0)
752 *Log << "Property " << pi->name << " of "
753 << param_name << " does not exist.\n";
754 // Find the property in the template from the class.
756 String property_type("STATIC");
757 double property_minimum = 0.0;
758 double property_maximum = 0.0;
759 std::list<param_property>::iterator ti = parameter_template.begin();
761 while (ti != parameter_template.end() && ti->name != pi->name)
765 if (ti != parameter_template.end())
767 property_type = ti->type;
768 property_minimum = ti->minimum;
769 property_maximum = ti->maximum;
772 insertion = "insert into property (paramid, name, value, type, min, max) values ('";
773 insertion += paramid + "', '";
774 insertion += pi->name + "', '";
775 insertion += pi->value + "', '";
776 insertion += property_type + "', '";
777 insertion += String(property_minimum) + "', '";
778 insertion += String(property_maximum) + "')";
779 database.Query(insertion);
781 insertion = "insert into history (paramid, modified,";
782 insertion += " change_nature, changed_property, new_value)";
783 insertion += " values ('";
784 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
786 insertion += pi->name + "', '";
787 insertion += pi->value + "')";
788 database.Query(insertion);
792 param_property stored_property;
794 stored_property.value = database.Field(0, "value");
795 stored_property.type = database.Field(0, "type");
796 stored_property.minimum = database.Field(0, "min");
797 stored_property.maximum = database.Field(0, "max");
799 if (stored_property.value != pi->value)
801 *Log << "Property " << pi->name << " of "
802 << param_name << " is different.\n";
804 insertion = "update property set value='";
805 insertion += pi->value + "' where paramid='";
806 insertion += paramid + "' and name='";
807 insertion += pi->name + "'";
809 database.Query(insertion);
811 insertion = "insert into history (paramid, modified,";
812 insertion += " change_nature, changed_property, new_value)";
813 insertion += " values ('";
814 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
815 + "', 'MODIFIED', '";
816 insertion += pi->name + "', '";
817 insertion += pi->value + "')";
819 database.Query(insertion);
820 if (stored_property.type == "DYNAMIC")
822 // Check the value against the range of the property.
824 double numeric_value = pi->value;
825 if (numeric_value < stored_property.minimum || numeric_value > stored_property.maximum)
832 // A STATIC property changed.
833 param_changed = true;
843 if (change_notification == "")
845 remark = "Gnucomo detected a different property for parameter(s) ";
846 change_notification = database.new_notification(objectid,
847 "property modified", remark);
850 if (change_notification != "")
852 qry = "select * from parameter_notification where notificationid='";
853 qry += change_notification + "' and paramid='";
854 qry += paramid + "'";
856 if (database.Query(qry) == 0)
858 insertion = "insert into parameter_notification";
859 insertion += " (notificationid, paramid) values ('";
860 insertion += change_notification + "', '";
861 insertion += paramid + "')";
863 database.Query(insertion);
868 *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
874 if (out_of_range_notification == "")
876 remark = "Gnucomo detected that a property value is out of range.";
877 out_of_range_notification = database.new_notification(objectid,
878 "property out of range", remark);
881 if (out_of_range_notification != "")
883 qry = "select * from parameter_notification where notificationid='";
884 qry += out_of_range_notification + "' and paramid='";
885 qry += paramid + "'";
887 if (database.Query(qry) == 0)
889 insertion = "insert into parameter_notification";
890 insertion += " (notificationid, paramid) values ('";
891 insertion += out_of_range_notification + "', '";
892 insertion += paramid + "')";
894 database.Query(insertion);
899 *Log << "gcm_input ERROR: Cannot create 'property out of range' notification.\n";
905 // The parameter does not exist; create anew.
907 // TODO: Insert description
909 insertion = "insert into parameter (objectid, name, class, description) values ('";
910 insertion += objectid + "', '" + param_name + "', '" + param_class + "', '')";
911 database.Query(insertion);
913 qry = "select paramid from parameter where objectid='";
914 qry += objectid + "' and class='";
915 qry += param_class + "' and name='";
916 qry += param_name + "'";
918 paramid = database.Field(0, "paramid");
920 while (pi != properties.end())
922 // Find the property in the template from the class.
924 String property_type("STATIC");
925 double property_minimum = 0.0;
926 double property_maximum = 0.0;
927 std::list<param_property>::iterator ti = parameter_template.begin();
929 while (ti != parameter_template.end() && ti->name != pi->name)
933 if (ti != parameter_template.end())
935 property_type = ti->type;
936 property_minimum = ti->minimum;
937 property_maximum = ti->maximum;
940 insertion = "insert into property (paramid, name, value, type, min, max) values ('";
941 insertion += paramid + "', '";
942 insertion += pi->name + "', '";
943 insertion += pi->value + "', '";
944 insertion += property_type + "', '";
945 insertion += String(property_minimum) + "', '";
946 insertion += String(property_maximum) + "')";
947 database.Query(insertion);
949 insertion = "insert into history (paramid, modified,";
950 insertion += " change_nature, changed_property, new_value)";
951 insertion += " values ('";
952 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
954 insertion += pi->name + "', '";
955 insertion += pi->value + "')";
956 database.Query(insertion);
963 if (create_notification == "")
965 remark = "Gnucomo detected new parameter(s) of class " + param_class;
966 create_notification = database.new_notification(objectid,
967 "parameter created", remark);
969 if (create_notification != "")
971 insertion = "insert into parameter_notification";
972 insertion += " (notificationid, paramid) values ('";
973 insertion += create_notification + "', '";
974 insertion += paramid + "')";
976 database.Query(insertion);
980 *Log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
991 // Check if any parameters in this class have disappeared.
993 qry = "select name, paramid from parameter where objectid='";
994 qry += objectid + "' and class='" + param_class + "'";
996 int nr_parameters = database.Query(qry);
997 pqxx::Result parameter_set = database.Result();
999 for (int i = 0; i < nr_parameters; i++)
1002 String param_name, paramid;
1004 param_name = database.Field(parameter_set, i, "name");
1005 XPath = "gcmt:parameter[@name='" + param_name + "']";
1007 res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
1008 if (res->nodesetval->nodeTab == NULL)
1010 // The parameter is in the database but not in the report
1013 *Log << "Could not find " << XPath << " in XML tree.\n";
1015 paramid = database.Field(parameter_set, i, "paramid");
1016 qry ="select change_nature from history where paramid='";
1017 qry += paramid + "' order by modified desc";
1018 if (database.Query(qry) <= 0)
1020 *Log << "Database ERROR: no history record for parameter "
1021 << param_name << ".\n";
1023 else if (database.Field(0, "change_nature") != "REMOVED")
1027 *Log << "Removing parameter " << param_name << ".\n";
1030 insertion = "insert into history (paramid, modified, change_nature)";
1031 insertion += " values ('";
1032 insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
1035 database.Query(insertion);
1037 if (remove_notification == "")
1039 remark = "Gnucomo detected that " + param_class
1040 + " parameters(s) have disappeared ";
1041 remove_notification = database.new_notification(objectid,
1042 "parameter removed", remark);
1045 if (remove_notification != "")
1047 insertion = "insert into parameter_notification";
1048 insertion += " (notificationid, paramid) values ('";
1049 insertion += remove_notification + "', '";
1050 insertion += paramid + "')";
1052 database.Query(insertion);
1056 *Log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
1065 *Log << "Data element " << node->name << " is not supported.\n";
1070 *Log << "Data node not found.\n";
1074 /*=========================================================================
1076 ** SYNOPSIS : int enter()
1078 ** RETURN VALUE : The number of lines successfully parsed from the input
1086 ** LAST MODIFIED : Nov 26, 2003
1087 **=========================================================================
1090 int client_message::enter()
1092 pan.mf->set_message_type(pan.lc->message_type());
1094 pan.mf->construct_XML(input, xmlBuffer);
1097 *Log << "Constructed XML document:\n\n";
1098 *Log << xmlBuffer.str();
1102 xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
1106 if (extractHeader())
1113 *Log << "XML parser FAILED.\n";