Fix PR10: Gcm_input may loose its input message
[gnucomo.git] / src / gcm_input / message.cpp
index 7e727ff..9393a16 100644 (file)
@@ -8,7 +8,7 @@
 ***********************
 **      FILE NAME      : message.cpp
 **      SYSTEM NAME    : Gnucomo - Gnu Computer Monitoring
-**      VERSION NUMBER : $Revision: 1.16 $
+**      VERSION NUMBER : $Revision: 1.19 $
 **
 **  DESCRIPTION      :  Implementation of the message handling classes
 **
 ********************************
 **      ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
 **      CREATION DATE   : Sep 16, 2002
-**      LAST UPDATE     : Nov 28, 2003
+**      LAST UPDATE     : Nov 02, 2007
 **      MODIFICATIONS   : 
 **************************************************************************/
 
 /*****************************
    $Log: message.cpp,v $
-   Revision 1.16  2003-12-04 10:38:09  arjen
+   Revision 1.19  2011-03-24 10:20:37  arjen
+   Added some debug info
+
+   Revision 1.18  2007/11/03 10:23:53  arjen
+   Handling of parameters is greatly improved.
+   When creating a new parameter from an XML report which is fed into
+   gcm_input, the class definition is used as a template to fill in
+   the default values for the properties.
+   The class is also used as a template when a new property is added
+   to an existing parameter.
+
+   DYNAMIC properties are now handled properly. Instead of making a
+   'changed property' notification, the value is checked against the
+   defined range for the property. An 'out of range' notification
+   is created when this condition is detected.
+
+   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
 
 *****************************/
 
-static const char *RCSID = "$Id: message.cpp,v 1.16 2003-12-04 10:38:09 arjen Exp $";
+#include <unistd.h>
 
+#include <fstream>
 #include <algorithm>
 #include <libxml/xpath.h>
 #include <libxml/debugXML.h>
+
 #include "message.h"
 
 //#define DEBUG
@@ -333,6 +354,10 @@ double client_message::classify(String host, UTC arriv, String serv)
 
    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)
@@ -359,10 +384,14 @@ double client_message::classify(String host, UTC arriv, String serv)
     *  out what the content of the message is.
     */
 
+#ifdef DEBUG
+   *Log << "Classifying message.\n";
+#endif
+
 
    uncertainty = 1.0;
 
-   while (input >> line && uncertainty > 0.1)
+   while (input >> line && uncertainty > epsilon)
    {
       if (verbose)
       {
@@ -404,6 +433,7 @@ double client_message::classify(String host, UTC arriv, String serv)
          classification = COOKER_OBJECT;
       }
    }
+
    input.rewind();
 
    certainty = 1.0 - uncertainty;
@@ -424,7 +454,7 @@ double client_message::classify(String host, UTC arriv, String serv)
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Nov 28, 2003
+**  LAST MODIFIED  : Nov 02, 2007
 **=========================================================================
 */
 
@@ -432,6 +462,9 @@ struct param_property
 {
    String name;
    String value;
+   String type;
+   double minimum;
+   double maximum;
 };
 
 void client_message::enterXML()
@@ -477,6 +510,8 @@ void client_message::enterXML()
       }
       if (strcmp((char *)node->name, "log") == 0)
       {
+         int log_entry_counter = 0;
+
          //  Each child contains a log entry, raw or cooked.
 
          node = node->children;
@@ -484,12 +519,18 @@ void client_message::enterXML()
          {
             if (node->type == XML_ELEMENT_NODE)
             {
+               log_entry_counter++;
+
                xmlNodePtr  item;
                String      log_hostname;
                UTC         log_date;
                String      raw("");;
                String      log_service;
 
+               if (verbose)
+               {
+                  *Log << "Log entry " << log_entry_counter << " is " << node->name << "\n";
+               }
                if (strcmp((char *)node->name, "raw") == 0 && node->children != NULL)
                {
                   item = node->children;
@@ -509,6 +550,11 @@ void client_message::enterXML()
                         }
                         log_service = pan.lc->service();
                         log_date    = pan.lc->timestamp();
+
+                        if (!log_date.proper())
+                        {
+                           *Log << log_date << " is not a valid timestamp.\n";
+                        }
                      }
                      else
                      {
@@ -562,7 +608,7 @@ void client_message::enterXML()
                   }
                   else
                   {
-                     *Log << "<timestamp> missing from cooked log element.\n";
+                     *Log << "<timestamp> missing from cooked log element nr " << log_entry_counter << ".\n";
                   }
 
                   res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
@@ -573,7 +619,7 @@ void client_message::enterXML()
                   }
                   else
                   {
-                     *Log << "<raw> missing from cooked log element.\n";
+                     *Log << "<raw> missing from cooked log element nr " << log_entry_counter << ".\n";
                   }
 
                }
@@ -603,6 +649,10 @@ void client_message::enterXML()
                   }
 
                }
+               else
+               {
+                  *Log << "Can not insert log element " << raw << ".\n";
+               }
  
             }
             node = node->next;
@@ -615,14 +665,39 @@ void client_message::enterXML()
          String   qry;
          String   insertion;
          String   change_notification("");
+         String   out_of_range_notification("");
          String   create_notification("");
          String   remove_notification("");
          bool     initial_entry = false;
          String   param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
 
+         std::list<param_property>  parameter_template;
+         int                        nr_properties;
+
+         //  Obtain a list of properties from the parameter's class.
+         //  This list is used to create new parameters.
+
+         qry = "select * from parameter_class where name='";
+         qry += param_class + "'";
+         nr_properties = database.Query(qry);
+         for (int i = 0; i < nr_properties; i++)
+         {
+            param_property pp;
+
+            pp.name = database.Field(i, "property_name");
+            pp.type = database.Field(i, "property_type");
+            pp.minimum = database.Field(i, "min");
+            pp.maximum = database.Field(i, "max");
+
+            parameter_template.push_back(pp);
+         }
+
+#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
+         //  If we don't have any parameters of this class, this will be
          //  an initial entry.
 
          qry = "select name from parameter where objectid='";
@@ -637,6 +712,9 @@ void client_message::enterXML()
             {
                String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
 
+#ifdef DEBUG
+               *Log << "Parameter with name " << param_name << "\n";
+#endif
                std::list<param_property>  properties;
                param_property             prop;
                xmlNodePtr                 item;
@@ -683,43 +761,101 @@ void client_message::enterXML()
                   //  The parameter exists in the database; check all properties.
 
                   bool  param_changed = false;
+                  bool  out_of_range  = false;
 
                   paramid = database.Field(0, "paramid");
                   while (pi != properties.end())
                   {
-                     qry = "select value from property where paramid='";
+                     qry = "select * 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";
+                        //  Find the property in the template from the class.
 
-                        insertion = "update property set value='";
-                        insertion += pi->value + "' where paramid='";
-                        insertion += paramid + "' and name='";
-                        insertion += pi->name + "'";
+                        String property_type("STATIC");
+                        double property_minimum = 0.0;
+                        double property_maximum = 0.0;
+                        std::list<param_property>::iterator ti = parameter_template.begin();
 
+                        while (ti != parameter_template.end() && ti->name != pi->name)
+                        {
+                           ti++;
+                        }
+                        if (ti != parameter_template.end())
+                        {
+                           property_type    = ti->type;
+                           property_minimum = ti->minimum;
+                           property_maximum = ti->maximum;
+                        }
+
+                        insertion = "insert into property (paramid, name, value, type, min, max) values ('";
+                        insertion += paramid + "', '";
+                        insertion += pi->name + "', '";
+                        insertion += pi->value + "', '";
+                        insertion += property_type + "', '";
+                        insertion += String(property_minimum) + "', '";
+                        insertion += String(property_maximum) + "')";
                         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', '";
+                                             + "', 'CREATED', '";
                         insertion += pi->name + "', '";
                         insertion += pi->value + "')";
-
                         database.Query(insertion);
+                     }
+                     else
+                     {
+                        param_property  stored_property;
 
-                        param_changed = true;
+                        stored_property.value    = database.Field(0, "value");
+                        stored_property.type     = database.Field(0, "type");
+                        stored_property.minimum  = database.Field(0, "min");
+                        stored_property.maximum  = database.Field(0, "max");
 
+                        if (stored_property.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);
+                           if (stored_property.type == "DYNAMIC")
+                           {
+                              // Check the value against the range of the property.
+
+                              double numeric_value = pi->value;
+                              if (numeric_value < stored_property.minimum || numeric_value > stored_property.maximum)
+                              {
+                                 out_of_range = true;
+                              }
+                           }
+                           else
+                           {
+                              //  A STATIC property changed.
+                              param_changed = true;
+                           }
+
+                        }
                      }
                      pi++;
                   }
@@ -728,7 +864,7 @@ void client_message::enterXML()
                   {
                      if (change_notification == "")
                      {
-                        remark = "Gnucomo detected a different version for package parameter(s) ";
+                        remark = "Gnucomo detected a different property for parameter(s) ";
                         change_notification = database.new_notification(objectid,
                                                            "property modified", remark);
                      }
@@ -754,6 +890,37 @@ void client_message::enterXML()
                         *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
                      }
                   }
+
+                  if (out_of_range)
+                  {
+                     if (out_of_range_notification == "")
+                     {
+                        remark = "Gnucomo detected that a property value is out of range.";
+                        out_of_range_notification = database.new_notification(objectid,
+                                                           "property out of range", remark);
+                     }
+
+                     if (out_of_range_notification != "")
+                     {
+                        qry = "select * from parameter_notification where notificationid='";
+                        qry += out_of_range_notification + "' and paramid='";
+                        qry += paramid + "'";
+
+                        if (database.Query(qry) == 0)
+                        {
+                           insertion = "insert into parameter_notification";
+                           insertion += " (notificationid, paramid) values ('";
+                           insertion += out_of_range_notification + "', '";
+                           insertion += paramid + "')";
+
+                           database.Query(insertion);
+                        }
+                     }
+                     else
+                     {
+                        *Log << "gcm_input ERROR: Cannot create 'property out of range' notification.\n";
+                     }
+                  }
                }
                else
                {
@@ -774,10 +941,31 @@ void client_message::enterXML()
 
                   while (pi != properties.end())
                   {
-                     insertion = "insert into property (paramid, name, value, type) values ('";
+                     //  Find the property in the template from the class.
+
+                     String property_type("STATIC");
+                     double property_minimum = 0.0;
+                     double property_maximum = 0.0;
+                     std::list<param_property>::iterator ti = parameter_template.begin();
+
+                     while (ti != parameter_template.end() && ti->name != pi->name)
+                     {
+                        ti++;
+                     }
+                     if (ti != parameter_template.end())
+                     {
+                        property_type    = ti->type;
+                        property_minimum = ti->minimum;
+                        property_maximum = ti->maximum;
+                     }
+
+                     insertion = "insert into property (paramid, name, value, type, min, max) values ('";
                      insertion += paramid + "', '";
                      insertion += pi->name + "', '";
-                     insertion += pi->value + "', 'STATIC')";
+                     insertion += pi->value + "', '";
+                     insertion += property_type + "', '";
+                     insertion += String(property_minimum) + "', '";
+                     insertion += String(property_maximum) + "')";
                      database.Query(insertion);
 
                      insertion = "insert into history (paramid, modified,";
@@ -796,7 +984,7 @@ void client_message::enterXML()
                   {
                      if (create_notification == "")
                      {
-                        remark = "Gnucomo detected new parameter(s) of class package";
+                        remark = "Gnucomo detected new parameter(s) of class " + param_class;
                         create_notification = database.new_notification(objectid,
                                                          "parameter created", remark);
                      }
@@ -824,25 +1012,41 @@ void client_message::enterXML()
          {
             //  Check if any parameters in this class have disappeared.
 
+#ifdef DEBUG
+            *Log << "Checking for disappeared parameters.\n";
+#endif
             qry = "select name, paramid from parameter where objectid='";
             qry += objectid + "' and class='" + param_class + "'";
 
             int          nr_parameters = database.Query(qry);
-            pqxx::Result parameter_set = database.Result();
+            pqxx::result parameter_set = database.Result();
 
+#ifdef DEBUG
+            *Log << nr_parameters << " parameters of class " << param_class << " found in the database.\n";
+#endif
             for (int i = 0; i < nr_parameters; i++)
             {
                String XPath;
                String param_name, paramid;
 
                param_name = database.Field(parameter_set, i, "name");
+#ifdef DEBUG
+               *Log << "Looking for " << param_name << " in XML tree.\n";
+#endif
                XPath = "gcmt:parameter[@name='" + param_name + "']";
 
                res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
-               if (res->nodesetval->nodeTab == NULL)
+#ifdef DEBUG
+               *Log << "XPATH result: " << res->type << ".\n";
+               *Log << "Nr of nodes found: " << res->nodesetval->nodeNr << ".\n";
+#endif
+               if (res->nodesetval->nodeNr == 0)
                {
                   // 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";
@@ -867,7 +1071,8 @@ void client_message::enterXML()
 
                      if (remove_notification == "")
                      {
-                        remark = "Gnucomo detected that package(s) have disappeared ";
+                        remark = "Gnucomo detected that " + param_class
+                               + " parameters(s) have disappeared ";
                         remove_notification = database.new_notification(objectid,
                                                          "parameter removed", remark);
                      }
@@ -887,6 +1092,12 @@ void client_message::enterXML()
                      }
                   }
                }
+               else
+               {
+#ifdef DEBUG
+                  *Log << XPath << " was found in XML tree.\n";
+#endif
+               }
             }
          }
       }
@@ -901,11 +1112,23 @@ void client_message::enterXML()
    }
 }
 
+void client_message::saveXML(String filename)
+{
+   std::ofstream    xmlfile;
+
+   xmlfile.open(filename);
+   xmlfile << xmlBuffer.str();
+}
+
 /*=========================================================================
 **  NAME           : enter
 **  SYNOPSIS       : int enter()
 **  PARAMETERS     : 
 **  RETURN VALUE   : The number of lines successfully parsed from the input
+**                   or a negative number if an error is encountered.
+**                   The error return values are:
+**                    -1  No database connection.
+**                    -2  XML parser error.
 **
 **  DESCRIPTION    : 
 **
@@ -913,12 +1136,14 @@ void client_message::enterXML()
 **  VARS CHANGED   :
 **  FUNCTIONS USED :
 **  SEE ALSO       :
-**  LAST MODIFIED  : Nov 26, 2003
+**  LAST MODIFIED  : Sep 22, 2020
 **=========================================================================
 */
 
 int client_message::enter()
 {
+   int nr_lines = 0;
+
    pan.mf->set_message_type(pan.lc->message_type());
 
    pan.mf->construct_XML(input, xmlBuffer);
@@ -931,18 +1156,27 @@ int client_message::enter()
 
    xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
 
-   if (xmlDom)
+   if (database.is_connected())
    {
-      if (extractHeader())
+      if (xmlDom)
+      {
+         if (extractHeader())
+         {
+            enterXML();
+         }
+      }
+      else
       {
-         enterXML();
+         *Log << "XML parser FAILED.\n";
+         nr_lines = -2;   //  XML parse error
       }
    }
    else
    {
-      *Log << "XML parser FAILED.\n";
+      *Log << "Database connection FAILED.\n";
+      nr_lines = -1;  // No database connection
    }
 
-   return 0;
+   return nr_lines;
 
 }