/**************************************************************************
** (c) Copyright 2002, Andromeda Technology & Automation
+** This is free software; you can redistribute it and/or modify it under the
+** terms of the GNU General Public License, see the file COPYING.
***************************************************************************
** MODULE INFORMATION *
***********************
** FILE NAME : message.cpp
** SYSTEM NAME : Gnucomo - Gnu Computer Monitoring
-** VERSION NUMBER : $Revision: 1.2 $
+** VERSION NUMBER : $Revision: 1.17 $
**
** DESCRIPTION : Implementation of the message handling classes
**
********************************
** ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
** CREATION DATE : Sep 16, 2002
-** LAST UPDATE : Nov 04, 2002
+** LAST UPDATE : Nov 28, 2003
** MODIFICATIONS :
**************************************************************************/
/*****************************
$Log: message.cpp,v $
- Revision 1.2 2002-11-04 10:13:36 arjen
+ Revision 1.17 2005-05-31 05:51:41 arjen
+ Textual changes in parameter notifications
+
+ Revision 1.16 2003/12/04 10:38:09 arjen
+ Major redesign. All input is handled through XML. Raw input data is first
+ transformed into an XML document for further processing.
+ A collection of polymorphic classes handle the transformation of various
+ input formats into XML.
+ Classifying input data is done with a finite improbability calculation.
+
+ Revision 1.15 2003/10/27 11:28:27 arjen
+ Do not add another parameter_notification record is the notification
+ already exists for that parameter.
+
+ Revision 1.14 2003/09/01 06:57:14 arjen
+ Reject log entries that are found to be invalid.
+
+ Revision 1.13 2003/08/16 15:28:45 arjen
+ Fixed a namespace problem
+
+ Revision 1.12 2003/08/11 16:56:16 arjen
+ Different kinds of log files are parsed by a collection of objects
+ of different classes, derived from the base class line_cooker
+ Depending on the message content or the message_type element in
+ XML, one of these objects is selected.
+
+ Logrunner is integrated with gcm_input. Although its functionality
+ is still limited, a connection between logrunner and gcm_input
+ is beginning to form.
+
+ Revision 1.11 2003/08/05 08:15:00 arjen
+ Debug output to the log stream instead of cerr.
+ Fixed namespace problems in XPath searches of the DOM.
+ Moved string utility functions to a separate file.
+
+ Revision 1.10 2003/04/29 09:16:44 arjen
+ Read XML input,
+ Only cooked log entries for now.
+
+ Revision 1.9 2003/03/29 09:04:10 arjen
+ Extract the hostname out of the 'From:' or 'Message-Id:' line
+ of an email header.
+
+ Revision 1.8 2003/03/16 09:42:40 arjen
+ Read IRIX system logs.
+
+ Revision 1.7 2003/02/21 08:08:05 arjen
+ Gcm_input also detects packages that are removed from the system.
+ Determining the version number of a package in a RPM
+ list is improved. Only the last one or two parts of the string that
+ begin with a '-' and a number are considered the version.
+
+ Revision 1.6 2003/02/05 09:37:51 arjen
+ Create notifications when a new package is discovered
+ in a 'rpm -qa' list or when the version of a package is changed.
+
+ Revision 1.4 2002/12/06 22:26:28 arjen
+ Set the value of log.processed to FALSE when inserting a
+ new log entry into the database
+ When a syslog entry arrives from last year, gcm_input subtracts one from the
+ year of arrival to create the year of the log entry.
+ Read output from "rpm -qa" and enter packages in the parameter table.
+
+ Revision 1.3 2002/11/09 08:04:27 arjen
+ Added a reference to the GPL
+
+ Revision 1.2 2002/11/04 10:13:36 arjen
Use proper namespace for iostream classes
Revision 1.1 2002/10/05 10:25:49 arjen
*****************************/
-static const char *RCSID = "$Id: message.cpp,v 1.2 2002-11-04 10:13:36 arjen Exp $";
+static const char *RCSID = "$Id: message.cpp,v 1.17 2005-05-31 05:51:41 arjen Exp $";
+#include <algorithm>
+#include <libxml/xpath.h>
+#include <libxml/debugXML.h>
#include "message.h"
+//#define DEBUG
+
extern bool verbose; /* Defined in the main application */
extern bool testmode;
+extern bool incremental;
+extern std::ostream *Log;
/* Utility functions */
-String SQL_Escape(String s);
+extern String SQL_Escape(String s);
/*=========================================================================
** NAME : operator >>
b.buffer.push_back(l);
// next_line keeps pointing to the end.
-
+
s = l;
input_ok = true;
}
return input_ok;
}
+/*=========================================================================
+** NAME : client_message
+** SYNOPSIS : client_message(std::istream *in, gnucomo_database db)
+** PARAMETERS :
+** RETURN VALUE : None
+**
+** DESCRIPTION : Client message constructor.
+**
+** VARS USED :
+** VARS CHANGED :
+** FUNCTIONS USED :
+** SEE ALSO :
+** LAST MODIFIED : Nov 04, 2002
+**=========================================================================
+*/
+
client_message::client_message(std::istream *in, gnucomo_database db)
{
input.from(in);
mail_header = false;
gpg_encrypted = false;
classification = UNKNOWN;
+ xmlDom = NULL;
certainty = 0.0;
}
-static const String syslog_date_re("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
-static const String mail_date_re("[[:alpha:]]{3}, [ 123]?[0-9] [[:alpha:]]{3} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} [+-][0-9]{4}");
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}");
-static const regex re_syslog(syslog_date_re + " [a-z]+ [[:alpha:]]+.*:.+");
static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
static const regex re_dump("^ *DUMP: Date of this level");
-static const regex re_accesslog("(GET|POST) .+ HTTP");
-static const regex re_errorlog("^\\[" + unix_date_re + "\\] \\[(error|notice)\\] .+");
+static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
+
+static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
+static const regex re_xml_header("xml .*\?>$");
+
+/*=========================================================================
+** NAME : extractHeader
+** SYNOPSIS : void extractHeader()
+** PARAMETERS :
+** RETURN VALUE : True if the mandatory header elements are available.
+**
+** DESCRIPTION : Extract the header information from the XML DOM tree.
+**
+** VARS USED :
+** VARS CHANGED :
+** FUNCTIONS USED :
+** SEE ALSO :
+** LAST MODIFIED : Nov 26, 2003
+**=========================================================================
+*/
+
+bool client_message::extractHeader()
+{
+ xmlNodePtr root, item;
+ xmlNsPtr namespaces[1];
+
+ xmlXPathObjectPtr res;
+ xmlXPathContextPtr pathcontext;
+
+ bool header_OK = true;
+
+ root = xmlDocGetRootElement(xmlDom);
+ namespaces[0] = root->ns;
+
+ //TODO Ought to check root->name and root->ns->href
+
+ pathcontext = xmlXPathNewContext(xmlDom);
+ pathcontext->node = xmlDocGetRootElement(xmlDom);
+ pathcontext->namespaces = namespaces;
+ pathcontext->nsNr = 1;
+
+#ifdef DEBUG
+ xmlDebugDumpNodeList(stdout, pathcontext->node, 0);
+#endif
+
+ res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:messagetype/text()", pathcontext);
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+
+ // Select a line cooker based on the message type.
+
+#ifdef DEBUG
+ *Log << "Looking for a line cooker for " << item->content << "\n";
+#endif
+ pan.lc = 0;
+ std::list<xform>::iterator lci = kitchen.begin();
+ while (pan.lc == 0 && lci != kitchen.end())
+ {
+ if (lci->lc->message_type() == (const char *)(item->content))
+ {
+ pan.lc = lci->lc;
+ }
+ lci++;
+ }
+ if (pan.lc == 0)
+ {
+ *Log << "Can not find a line cooker for message type " << item->content << "\n";
+ header_OK = false;
+ }
+ }
+ else
+ {
+ *Log << "Message type not found in XML header.\n";
+ header_OK = false;
+ }
+
+ res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:hostname/text()", pathcontext);
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ hostname = (const char *)item->content;
+ }
+ else
+ {
+ *Log << "Can not determine the hostname where the message came from.\n";
+ header_OK = false;
+ }
+
+ res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:service/text()", pathcontext);
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ service = (const char *)item->content;
+ }
+ res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:time/text()", pathcontext);
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ arrival = String((char *)item->content);
+ if (!arrival.proper())
+ {
+ *Log << "Arrival time is not properly stated.\n";
+ header_OK = false;
+ }
+
+ }
+ //xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
+
+ return header_OK;
+}
-static const regex re_syslog_date("[[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2}");
-static const regex re_uxmail_from("^From - " + unix_date_re);
-static const regex re_mail_From("^From:[[:blank:]]+");
-static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
-static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
-static const regex re_email_user("[[:alnum:]_.-]+@");
/*=========================================================================
** NAME : classify
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED : Nov 04, 2002
+** LAST MODIFIED : Nov 27, 2003
**=========================================================================
*/
arrival = arriv;
service = serv;
+ const double epsilon = 0.1; // Threshold for uncertainty
+ const double P = 0.5; // Probability of a wrong match
+
+ double uncertainty;
+
+#ifdef DEBUG
+ *Log << "Checking for a mail header.\n";
+#endif
+
/* First, check if the message has a mail header. */
if (input >> line && line == re_uxmail_from)
mail_header = true;
- /* Scan ahead for the hostname and date of arrival. */
+ /* Skip the mail header until the first empty line. */
while (input >> line && line != "")
{
- if (line == re_mail_From)
- {
- from_address = line(re_email_address);
- from_address(re_email_user) = ""; // Remove the user part;
- hostname = from_address;
- }
- if (line == re_mail_Date)
- {
- arrival = UTC(line(regex(mail_date_re)));
- }
}
}
else
{
// Push the first line back, we need to read it again.
--input;
+
}
/*
* out what the content of the message is.
*/
+#ifdef DEBUG
+ *Log << "Classifying message.\n";
+#endif
- while (input >> line && certainty < 0.9)
+
+ uncertainty = 1.0;
+
+ while (input >> line && uncertainty > 0.1)
{
- std::cout << " testing: " << line << "\n";
- if (line == re_syslog)
+ if (verbose)
{
- certainty = 1.0;
- classification = SYSLOG;
- if (verbose)
- {
- std::cout << "Syslog detected.\n";
- }
+ *Log << " testing: " << line << "\n";
}
- else if (line == re_PGP)
+
+ if (line == re_PGP)
{
- certainty = 1.0;
+ uncertainty = 0.0;
gpg_encrypted = true;
- std::cerr << "The message is PGP/GnuPG encrypted.\n";
+ *Log << "The message is PGP/GnuPG encrypted.\n";
}
- else if (line == re_dump)
- {
- certainty = 1.0;
- if (verbose)
- {
- std::cout << "DUMP output detected.\n";
- }
- }
- else if (line == re_accesslog)
- {
- certainty = 1.0;
- classification = ACCESSLOG;
- service = "httpd";
- if (verbose)
- {
- std::cout << "HTTP access log detected.\n";
- }
- }
- else if (line == re_errorlog)
+ else
{
- certainty = 1.0;
- classification = ERRORLOG;
- service = "httpd";
- if (verbose)
- {
- std::cout << "HTTP error log detected.\n";
- }
+ // Scan the list of line cookers if there is anything familiar.
+
+ std::list<xform>::iterator lci = kitchen.begin();
+ uncertainty = 1.0;
+ while (lci != kitchen.end())
+ {
+ if (lci->lc->check_pattern(line))
+ {
+ // We have a match; decrease the uncertainty
+
+ lci->uncertainty *= P;
+ if (uncertainty > lci->uncertainty)
+ {
+ uncertainty = lci->uncertainty;
+ pan = *lci;
+ }
+ if (verbose)
+ {
+ *Log << lci->lc->message_type() << " detected with "
+ << lci->uncertainty << " uncertainty.\n";
+ }
+ }
+ lci++;
+ }
+ classification = COOKER_OBJECT;
}
}
+
+ //TODO: If uncertainty is still too great, pick the least uncertain.
+
input.rewind();
- if (hostname == "")
- {
- std::cerr << "Can not determine the hostname where the message came from.\n";
- certainty = 0.0;
- }
- else if (!arrival.proper())
- {
- std::cerr << "Arrival time is not knwon.\n";
- certainty = 0.0;
- }
- else
- {
- certainty = 1.0;
- }
+ certainty = 1.0 - uncertainty;
return certainty;
}
/*=========================================================================
-** NAME : enter
-** SYNOPSIS : int enter()
+** NAME : enterXML
+** SYNOPSIS : int enterXML()
** PARAMETERS :
-** RETURN VALUE : The number of lines successfully parsed from the input
+** RETURN VALUE : None
**
-** DESCRIPTION :
+** DESCRIPTION : Analyze the DOM tree from the XML input.
+** The DOM tree was previously parsed by readXMLinput().
**
** VARS USED :
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED : Nov 04, 2002
+** LAST MODIFIED : Nov 28, 2003
**=========================================================================
*/
-int client_message::enter()
+struct param_property
{
- long nr_lines = 0;
- String line;
+ String name;
+ String value;
+};
- /* Double-check the classification of the message */
-
- if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
- {
- return 0;
- }
+void client_message::enterXML()
+{
+ //TODO : return the number of elements that are handled.
- if (mail_header)
- {
- // Skip the mail header.
-
- while (input >> line && line != "");
- }
+ xmlXPathObjectPtr res;
+ xmlXPathContextPtr pathcontext;
+ xmlNsPtr namespaces[1];
/* Try to find the host in the database */
String objectid;
+ String remark; // For notifications
objectid = database.find_host(hostname);
if (objectid == "")
{
- std::cerr << "Please define the host " << hostname << " in the database.\n";
- return 0;
+ *Log << "Please define the host " << hostname << " in the database.\n";
+ return;
}
if (verbose)
{
- std::cout << "Object id for " << hostname << " is " << objectid << "\n";
+ *Log << "Object id for " << hostname << " is " << objectid << "\n";
}
- /* Scan the input line by line, entring records into the database */
+ pathcontext = xmlXPathNewContext(xmlDom);
+ pathcontext->node = xmlDocGetRootElement(xmlDom);
+ namespaces[0] = pathcontext->node->ns;
+ pathcontext->namespaces = namespaces;
+ pathcontext->nsNr = 1;
- String rest; // Rest of the line to be parsed
+ res = xmlXPathEval((const xmlChar *)"gcmt:data/node()", pathcontext);
- while (input >> line)
+ if (res->nodesetval != NULL)
{
- if (verbose)
+ // Find the first child element of the <data> element.
+
+ xmlNodePtr node = *res->nodesetval->nodeTab;
+ while (node->type != XML_ELEMENT_NODE)
{
- std::cout << line << "\n";
+ node = node->next;
}
+ if (strcmp((char *)node->name, "log") == 0)
+ {
+ // Each child contains a log entry, raw or cooked.
+ node = node->children;
+ while (node != NULL)
+ {
+ if (node->type == XML_ELEMENT_NODE)
+ {
+ xmlNodePtr item;
+ String log_hostname;
+ UTC log_date;
+ String raw("");;
+ String log_service;
- /* Check each line if it contains valid information */
+ if (strcmp((char *)node->name, "raw") == 0 && node->children != NULL)
+ {
+ item = node->children;
+ if (pan.lc == 0)
+ {
+ *Log << "Can not cook this type of <raw> log element.\n";
+ }
+ else
+ {
+ raw = String((const char *)item->content);
+ if (pan.lc->cook_this(raw, arrival))
+ {
+ log_hostname = pan.lc->hostname();
+ if (log_hostname == "")
+ {
+ log_hostname = hostname;
+ }
+ log_service = pan.lc->service();
+ log_date = pan.lc->timestamp();
+ }
+ else
+ {
+ *Log << "gcm_input WARNING: Not a valid line: " << raw << "\n";
+ raw = "";
+ }
+ }
+ }
+ else if (strcmp((char *)node->name, "cooked") == 0)
+ {
+ // Find the parts of the log entry
+
+ if (verbose)
+ {
+ *Log << "Analyzing cooked element.\n";
+ }
+ pathcontext->node = node;
+
+ res = xmlXPathEval((const xmlChar *)"hostname/text()", pathcontext);
+ if (res->nodesetval != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ log_hostname = (const char *)item->content;
+ if (log_hostname != hostname(0, ~log_hostname))
+ {
+ *Log << "Hostname " << log_hostname << " does not match.\n";
+ log_hostname = "";
+ }
+ }
+ else
+ {
+ log_hostname = hostname;
+ }
+
+ res = xmlXPathEval((const xmlChar *)"service/text()", pathcontext);
+ if (res->nodesetval != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ log_service = (const char *)item->content;
+ }
+ else
+ {
+ log_service = service;
+ }
+
+ res = xmlXPathEval((const xmlChar *)"timestamp/text()", pathcontext);
+ if (res->nodesetval != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ log_date = String((const char *)item->content);
+ }
+ else
+ {
+ *Log << "<timestamp> missing from cooked log element.\n";
+ }
+
+ res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
+ if (res->nodesetval != NULL)
+ {
+ item = *res->nodesetval->nodeTab;
+ raw = String((const char *)item->content);
+ }
+ else
+ {
+ *Log << "<raw> missing from cooked log element.\n";
+ }
- const regex *check;
+ }
- switch (classification)
- {
- case SYSLOG:
- check = &re_syslog;
- break;
- case ACCESSLOG:
- check = &re_accesslog;
- break;
- case ERRORLOG:
- check = &re_errorlog;
- break;
- }
+ // Insert a new log record into the database.
+ if (raw != "" && log_hostname != "" && log_date.proper())
+ {
+ String insertion("insert into log (objectid, servicecode,"
+ " object_timestamp, timestamp, rawdata, processed) values (");
+
+ /* Insert a new record into the log table */
+
+ insertion += "'" + objectid + "',";
+ insertion += "'" + log_service + "',";
+ insertion += "'" + log_date.format("%Y-%m-%d %T") + "',";
+ insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
+ insertion += "'" + SQL_Escape(raw) + "',FALSE";
+ insertion += ")";
+
+ if (testmode)
+ {
+ *Log << insertion << "\n";
+ }
+ else
+ {
+ database.Query(insertion);
+ }
- if (line == *check)
+ }
+
+ }
+ node = node->next;
+ }
+ }
+ else if (strcmp((char *)node->name, "parameters") == 0)
{
- date log_date;
- hour log_time;
- int i;
-
- String insertion("insert into log (objectid, servicecode,"
- " object_timestamp, timestamp, rawdata) values (");
- String datestring;
-
- switch (classification)
+ // Each child contains a parameter entry, with at least one property
+
+ String qry;
+ String insertion;
+ String change_notification("");
+ String create_notification("");
+ String remove_notification("");
+ bool initial_entry = false;
+ String param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
+
+#ifdef DEBUG
+ *Log << "Entering a list of " << param_class << " parameters.\n";
+#endif
+ pathcontext->node = node;
+
+ // If we don;t have any parameters of this class, this will be
+ // an initial entry.
+
+ qry = "select name from parameter where objectid='";
+ qry += objectid + "' and class='" + param_class + "'";
+ initial_entry = database.Query(qry) == 0;
+
+ node = node->children;
+ while (node != NULL)
{
- case SYSLOG:
- log_date = line;
- log_time = line;
- if (log_date.Year() < 0 || log_date.Year() > 2500)
+ if (node->type == XML_ELEMENT_NODE &&
+ strcmp((char *)node->name, "parameter") == 0)
{
- // The year is not in the log file. Assume the year of arrival
+ String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
- log_date = date(log_date.Day(), log_date.Month(), date(arrival).Year());
- }
+#ifdef DEBUG
+ *Log << "Parameter with name " << param_name << "\n";
+#endif
+ std::list<param_property> properties;
+ param_property prop;
+ xmlNodePtr item;
- if (verbose)
- {
- std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
- }
- rest = line << 16;
- i = rest.index(' ');
- if (rest(0,i) == hostname(0,i))
- {
- rest <<= i + 1;
- if (verbose)
- {
- std::cout << " Hostname matches.\n";
- std::cout << " rest = " << rest << "\n";
- }
- for (i = 0; isalpha(rest[i]) && i < ~rest; i++);
- if (verbose)
+ String paramid;
+
+
+ // Collect the parameter's properties.
+
+ item = node->children;
+ while (item != NULL)
{
- std::cout << " Service name = " << rest(0,i) << "\n";
+ if (item->type == XML_ELEMENT_NODE &&
+ strcmp((char *)item->name, "property") == 0)
+ {
+ prop.name = (const char *)xmlGetProp(item, (const xmlChar *)"name");
+ if (item->children != NULL)
+ {
+ prop.value = (const char *)item->children->content;
+ properties.push_back(prop);
+ }
+ else
+ {
+ *Log << "WARNING: Property " << prop.name << " has no value.\n";
+ }
+ }
+
+ // TODO: Hanlde description element
+
+ item = item->next;
}
- /* Insert a new record into the log table */
+ // Check the parameter in the database.
+
+ std::list<param_property>::iterator pi = properties.begin();
+
+ qry = "select paramid from parameter where objectid='";
+ qry += objectid + "' and class='";
+ qry += param_class + "' and name='";
+ qry += param_name + "'";
- insertion += "'" + objectid + "',";
- insertion += "'" + rest(0,i) + "',";
- insertion += "'" + log_date.format() + " " + log_time.format() + "',";
- insertion += "'" + arrival.format() + "',";
- insertion += "'" + SQL_Escape(line) + "'";
- insertion += ")";
-
- if (testmode)
+ if (database.Query(qry) == 1)
{
- std::cout << insertion << "\n";
+ // The parameter exists in the database; check all properties.
+
+ bool param_changed = false;
+
+ paramid = database.Field(0, "paramid");
+ while (pi != properties.end())
+ {
+ qry = "select value from property where paramid='";
+ qry += paramid + "' and name='";
+ qry += pi->name + "'";
+ if (database.Query(qry) == 0)
+ {
+ *Log << "Property " << pi->name << " of "
+ << param_name << " does not exist.\n";
+ }
+ else if (database.Field(0, "value") != pi->value)
+ {
+ *Log << "Property " << pi->name << " of "
+ << param_name << " is different.\n";
+
+ insertion = "update property set value='";
+ insertion += pi->value + "' where paramid='";
+ insertion += paramid + "' and name='";
+ insertion += pi->name + "'";
+
+ database.Query(insertion);
+
+ insertion = "insert into history (paramid, modified,";
+ insertion += " change_nature, changed_property, new_value)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'MODIFIED', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "')";
+
+ database.Query(insertion);
+
+ param_changed = true;
+
+
+ }
+ pi++;
+ }
+
+ if (param_changed)
+ {
+ if (change_notification == "")
+ {
+ remark = "Gnucomo detected a different property for parameter(s) ";
+ change_notification = database.new_notification(objectid,
+ "property modified", remark);
+ }
+
+ if (change_notification != "")
+ {
+ qry = "select * from parameter_notification where notificationid='";
+ qry += change_notification + "' and paramid='";
+ qry += paramid + "'";
+
+ if (database.Query(qry) == 0)
+ {
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
+ insertion += change_notification + "', '";
+ insertion += paramid + "')";
+
+ database.Query(insertion);
+ }
+ }
+ else
+ {
+ *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
+ }
+ }
}
else
{
+ // The parameter does not exist; create anew.
+
+ // TODO: Insert description
+
+ insertion = "insert into parameter (objectid, name, class, description) values ('";
+ insertion += objectid + "', '" + param_name + "', '" + param_class + "', '')";
database.Query(insertion);
- }
- if (verbose)
- {
- std::cout << "\n\n";
+ qry = "select paramid from parameter where objectid='";
+ qry += objectid + "' and class='";
+ qry += param_class + "' and name='";
+ qry += param_name + "'";
+ database.Query(qry);
+ paramid = database.Field(0, "paramid");
+
+ while (pi != properties.end())
+ {
+ insertion = "insert into property (paramid, name, value, type) values ('";
+ insertion += paramid + "', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "', 'STATIC')";
+ database.Query(insertion);
+
+ insertion = "insert into history (paramid, modified,";
+ insertion += " change_nature, changed_property, new_value)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'CREATED', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "')";
+ database.Query(insertion);
+
+ pi++;
+ }
+
+ if (!initial_entry)
+ {
+ if (create_notification == "")
+ {
+ remark = "Gnucomo detected new parameter(s) of class " + param_class;
+ create_notification = database.new_notification(objectid,
+ "parameter created", remark);
+ }
+ if (create_notification != "")
+ {
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
+ insertion += create_notification + "', '";
+ insertion += paramid + "')";
+
+ database.Query(insertion);
+ }
+ else
+ {
+ *Log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
+ }
+ }
}
-
- nr_lines++;
- }
- else
- {
- std::cerr << " Hostname " << rest(0,i) << " does not match.\n";
- }
- break;
-
- case ACCESSLOG:
- datestring = line(regex("\\[.+\\]"));
- datestring <<= 1;
- datestring >>= 1;
- datestring[datestring.index(':')] = ' ';
- log_date = datestring;
- log_time = datestring;
- if (verbose)
- {
- std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
- }
- insertion += "'" + objectid + "',";
- insertion += "'" + service + "',";
- insertion += "'" + log_date.format() + " " + log_time.format() + "',";
- insertion += "'" + arrival.format() + "',";
- insertion += "'" + SQL_Escape(line) + "'";
- insertion += ")";
-
- if (testmode)
- {
- std::cout << insertion << "\n";
- }
- else
- {
- database.Query(insertion);
}
- if (verbose)
- {
- std::cout << "\n\n";
- }
+ node = node->next;
+ }
- nr_lines++;
- break;
+ if (!incremental)
+ {
+ // Check if any parameters in this class have disappeared.
- case ERRORLOG:
- datestring = line(regex("\\[.+\\]"));
- datestring <<= 1;
- datestring >>= 1;
- log_date = datestring;
- log_time = datestring;
- if (verbose)
- {
- std::cout << " Log timestamp = " << log_date << " " << log_time << "\n";
- }
- insertion += "'" + objectid + "',";
- insertion += "'" + service + "',";
- insertion += "'" + log_date.format() + " " + log_time.format() + "',";
- insertion += "'" + arrival.format() + "',";
- insertion += "'" + SQL_Escape(line) + "'";
- insertion += ")";
-
- if (testmode)
- {
- std::cout << insertion << "\n";
- }
- else
- {
- database.Query(insertion);
- }
+ qry = "select name, paramid from parameter where objectid='";
+ qry += objectid + "' and class='" + param_class + "'";
- if (verbose)
+ int nr_parameters = database.Query(qry);
+ pqxx::Result parameter_set = database.Result();
+
+ for (int i = 0; i < nr_parameters; i++)
{
- std::cout << "\n\n";
- }
+ String XPath;
+ String param_name, paramid;
+
+ param_name = database.Field(parameter_set, i, "name");
+ XPath = "gcmt:parameter[@name='" + param_name + "']";
- nr_lines++;
- break;
+ res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
+ if (res->nodesetval->nodeTab == NULL)
+ {
+ // The parameter is in the database but not in the report
+
+#ifdef DEBUG
+ *Log << "Could not find " << XPath << " in XML tree.\n";
+#endif
+ paramid = database.Field(parameter_set, i, "paramid");
+ qry ="select change_nature from history where paramid='";
+ qry += paramid + "' order by modified desc";
+ if (database.Query(qry) <= 0)
+ {
+ *Log << "Database ERROR: no history record for parameter "
+ << param_name << ".\n";
+ }
+ else if (database.Field(0, "change_nature") != "REMOVED")
+ {
+ if (verbose)
+ {
+ *Log << "Removing parameter " << param_name << ".\n";
+ }
+
+ insertion = "insert into history (paramid, modified, change_nature)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'REMOVED')";
+
+ database.Query(insertion);
+
+ if (remove_notification == "")
+ {
+ remark = "Gnucomo detected that " + param_class
+ + " parameters(s) have disappeared ";
+ remove_notification = database.new_notification(objectid,
+ "parameter removed", remark);
+ }
+
+ if (remove_notification != "")
+ {
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
+ insertion += remove_notification + "', '";
+ insertion += paramid + "')";
+
+ database.Query(insertion);
+ }
+ else
+ {
+ *Log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
+ }
+ }
+ }
+ }
}
}
else
{
- std::cerr << "gcm_input WARNING: Not a valid line: " << line << "\n";
+ *Log << "Data element " << node->name << " is not supported.\n";
}
}
-
- if (verbose)
+ else
{
- std::cout << nr_lines << " lines parsed from the log file.\n";
+ *Log << "Data node not found.\n";
}
- return nr_lines;
}
/*=========================================================================
-** NAME : SQL_Escape
-** SYNOPSIS : String SQL_Escape(String)
+** NAME : enter
+** SYNOPSIS : int enter()
** PARAMETERS :
-** RETURN VALUE :
+** RETURN VALUE : The number of lines successfully parsed from the input
**
-** DESCRIPTION : Insert backslashes before single quotes.
+** DESCRIPTION :
**
** VARS USED :
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED :
+** LAST MODIFIED : Nov 26, 2003
**=========================================================================
*/
-String SQL_Escape(String s)
+int client_message::enter()
{
- int i;
+ pan.mf->set_message_type(pan.lc->message_type());
+
+ pan.mf->construct_XML(input, xmlBuffer);
- for (i = 0; i < ~s; i++)
+#ifdef DEBUG
+ *Log << "Constructed XML document:\n\n";
+ *Log << xmlBuffer.str();
+ *Log << "\n";
+#endif
+
+ xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
+
+ if (xmlDom)
{
- if (s[i] == '\'')
+ if (extractHeader())
{
- s(i,0) = "\\";
- i++;
+ enterXML();
}
}
+ else
+ {
+ *Log << "XML parser FAILED.\n";
+ }
+
+ return 0;
- return s;
}