diff --git a/TimberWinR.ServiceHost/sampleconf.xml b/TimberWinR.ServiceHost/sampleconf.xml
index c5a5bee..b39ad12 100644
--- a/TimberWinR.ServiceHost/sampleconf.xml
+++ b/TimberWinR.ServiceHost/sampleconf.xml
@@ -17,10 +17,11 @@
-
+
MMM d HH:mm:ss
MMM dd HH:mm:ss
diff --git a/TimberWinR.UnitTests/Configuration.cs b/TimberWinR.UnitTests/Configuration.cs
index 12cad81..1eb1a31 100644
--- a/TimberWinR.UnitTests/Configuration.cs
+++ b/TimberWinR.UnitTests/Configuration.cs
@@ -260,7 +260,7 @@ namespace TimberWinR.UnitTests
Assert.AreEqual(iCodepage, log.ICodepage);
Assert.AreEqual(recurse, log.Recurse);
Assert.AreEqual(splitLongLines, log.SplitLongLines);
- Assert.IsNull(log.ICheckpoint);
+
name = "Second Set";
location = @"C:\Logs2\*.log";
@@ -275,7 +275,6 @@ namespace TimberWinR.UnitTests
Assert.AreEqual(iCodepage, log.ICodepage);
Assert.AreEqual(recurse, log.Recurse);
Assert.AreEqual(splitLongLines, log.SplitLongLines);
- Assert.IsNull(log.ICheckpoint);
name = "Third Set";
@@ -291,7 +290,6 @@ namespace TimberWinR.UnitTests
Assert.AreEqual(iCodepage, log.ICodepage);
Assert.AreEqual(recurse, log.Recurse);
Assert.AreEqual(splitLongLines, log.SplitLongLines);
- Assert.IsNull(log.ICheckpoint);
}
[Test]
diff --git a/TimberWinR/Configuration.cs b/TimberWinR/Configuration.cs
index ecb7865..0967081 100644
--- a/TimberWinR/Configuration.cs
+++ b/TimberWinR/Configuration.cs
@@ -58,8 +58,15 @@ namespace TimberWinR
{
validateWithSchema(xmlConfFile, Properties.Resources.configSchema);
- parseConfInput(xmlConfFile);
- parseConfFilter(xmlConfFile);
+ try
+ {
+ parseConfInput(xmlConfFile);
+ parseConfFilter(xmlConfFile);
+ }
+ catch(Exception ex)
+ {
+ LogManager.GetCurrentClassLogger().Error(ex);
+ }
}
private static void validateWithSchema(string xmlConfFile, string xsdSchema)
@@ -69,8 +76,7 @@ namespace TimberWinR
// Ensure that the xml configuration file provided obeys the xsd schema.
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add("", XmlReader.Create(new StringReader(xsdSchema)));
-
-#if false
+#if true
bool errorsFound = false;
config.Validate(schemas, (o, e) =>
{
@@ -115,55 +121,40 @@ namespace TimberWinR
select el;
string tagName = "Inputs";
- if (inputs.Count() == 0)
- {
+ if (inputs.Count() == 0)
throw new TimberWinR.ConfigurationErrors.MissingRequiredTagException(tagName);
- }
-
+
// WINDOWS EVENTS
IEnumerable xml_events =
from el in inputs.Elements("WindowsEvents").Elements("Event")
select el;
- foreach (XElement e in xml_events)
- {
- WindowsEvent.Parse(_events, e);
- }
-
-
+ foreach (XElement e in xml_events)
+ WindowsEvent.Parse(_events, e);
// TEXT LOGS
IEnumerable xml_logs =
from el in inputs.Elements("Logs").Elements("Log")
select el;
- foreach (XElement e in xml_logs)
- {
- TailFileInput.Parse(_logs, e);
- }
-
-
+ foreach (XElement e in xml_logs)
+ TailFileInput.Parse(_logs, e);
// IIS LOGS
IEnumerable xml_iis =
from el in inputs.Elements("IISLogs").Elements("IISLog")
select el;
- foreach (XElement e in xml_iis)
- {
- IISLog.Parse(_iislogs, e);
- }
-
+ foreach (XElement e in xml_iis)
+ IISLog.Parse(_iislogs, e);
// IISW3C LOGS
IEnumerable xml_iisw3c =
from el in inputs.Elements("IISW3CLogs").Elements("IISW3CLog")
select el;
- foreach (XElement e in xml_iisw3c)
- {
- IISW3CLog.Parse(_iisw3clogs, e);
- }
+ foreach (XElement e in xml_iisw3c)
+ IISW3CLog.Parse(_iisw3clogs, e);
}
static void parseConfFilter(string xmlConfFile)
@@ -178,16 +169,20 @@ namespace TimberWinR
{
switch (e.Name.ToString())
{
+ case DateFilter.TagName:
+ DateFilter.Parse(_filters, e);
+ break;
case GrokFilter.TagName:
GrokFilter.Parse(_filters, e);
break;
case MutateFilter.TagName:
MutateFilter.Parse(_filters, e);
break;
+ default:
+ throw new Exception(string.Format("Unknown tag: {0}", e.Name.ToString()));
+ break;
}
}
}
-
-
}
}
\ No newline at end of file
diff --git a/TimberWinR/Filters/DateFilter.cs b/TimberWinR/Filters/DateFilter.cs
index 554fa08..f23cfcb 100644
--- a/TimberWinR/Filters/DateFilter.cs
+++ b/TimberWinR/Filters/DateFilter.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
+using System.Xml;
using Newtonsoft.Json.Linq;
using System.Xml.Linq;
@@ -10,6 +11,8 @@ namespace TimberWinR.Filters
{
public class DateFilter : FilterBase
{
+ public const string TagName = "Date";
+
public string Field { get; private set; }
public string Target { get; private set; }
public bool ConvertToUTC { get; private set; }
@@ -29,67 +32,13 @@ namespace TimberWinR.Filters
{
Patterns = new List();
- ParseField(parent);
- ParseTarget(parent);
- ParseConvertToUTC(parent);
+ Field = ParseStringAttribute(parent, "field");
+ Target = ParseStringAttribute(parent, "target", Field);
+ ConvertToUTC = ParseBoolAttribute(parent, "convertToUTC", false);
ParsePatterns(parent);
}
- private void ParseField(XElement parent)
- {
- string attributeName = "field";
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
- Field = a.Value;
- }
- catch
- {
- }
- }
-
- private void ParseTarget(XElement parent)
- {
- string attributeName = "field";
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
- Field = a.Value;
- }
- catch
- {
- }
- }
-
- private void ParseConvertToUTC(XElement parent)
- {
- string attributeName = "convertToUTC";
- string value;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- value = a.Value;
-
- if (value == "ON" || value == "true")
- {
- ConvertToUTC = true;
- }
- else if (value == "OFF" || value == "false")
- {
- ConvertToUTC = false;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(parent.Attribute(attributeName));
- }
- }
- catch { }
- }
-
+
private void ParsePatterns(XElement parent)
{
foreach (var e in parent.Elements("Pattern"))
@@ -148,6 +97,5 @@ namespace TimberWinR.Filters
else
json[Target] = ts;
}
-
}
}
diff --git a/TimberWinR/Filters/FilterBase.cs b/TimberWinR/Filters/FilterBase.cs
index e9a8e42..46162f1 100644
--- a/TimberWinR/Filters/FilterBase.cs
+++ b/TimberWinR/Filters/FilterBase.cs
@@ -2,12 +2,45 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Xml.Linq;
using Newtonsoft.Json.Linq;
namespace TimberWinR.Filters
{
public abstract class FilterBase
{
- public abstract void Apply(JObject json);
+ public abstract void Apply(JObject json);
+
+ protected static string ParseStringAttribute(XElement e, string attributeName, string defaultValue="")
+ {
+ string retValue = defaultValue;
+ XAttribute a = e.Attribute(attributeName);
+ if (a != null)
+ retValue = a.Value;
+ return retValue;
+ }
+
+ protected static bool ParseBoolAttribute(XElement e, string attributeName, bool defaultValue)
+ {
+ bool retValue = defaultValue;
+ XAttribute a = e.Attribute(attributeName);
+
+ if (a != null)
+ {
+ switch (a.Value)
+ {
+ case "ON":
+ case "true":
+ retValue = true;
+ break;
+
+ case "OFF":
+ case "false":
+ retValue = false;
+ break;
+ }
+ }
+ return retValue;
+ }
}
}
diff --git a/TimberWinR/Filters/GrokFilter.cs b/TimberWinR/Filters/GrokFilter.cs
index e48574f..0c6c9c0 100644
--- a/TimberWinR/Filters/GrokFilter.cs
+++ b/TimberWinR/Filters/GrokFilter.cs
@@ -44,20 +44,9 @@ namespace TimberWinR.Filters
private void ParseMatch(XElement parent)
{
XElement e = parent.Element("Match");
-
- if (e != null)
- {
- string attributeName = "value";
- try
- {
- Match = e.Attribute(attributeName).Value;
- }
- catch
- {
- throw new TimberWinR.ConfigurationErrors.MissingRequiredAttributeException(e, attributeName);
- }
- }
+ Match = e.Attribute("value").Value;
+ Field = e.Attribute("field").Value;
}
private void ParseAddFields(XElement parent)
@@ -177,7 +166,7 @@ namespace TimberWinR.Filters
var namedCaptures = regex.MatchNamedCaptures(text);
foreach (string fieldName in namedCaptures.Keys)
{
- AddOrModify(json, fieldName, namedCaptures[fieldName]);
+ AddOrModify(json, fieldName, namedCaptures[fieldName]);
}
}
}
diff --git a/TimberWinR/Inputs/InputBase.cs b/TimberWinR/Inputs/InputBase.cs
index d1c52a9..3759ad8 100644
--- a/TimberWinR/Inputs/InputBase.cs
+++ b/TimberWinR/Inputs/InputBase.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Data.Common;
using System.Linq;
using System.Xml.Linq;
@@ -52,5 +53,96 @@ namespace TimberWinR.Inputs
return fields;
}
+ protected static string ParseRequiredStringAttribute(XElement e, string attributeName)
+ {
+ XAttribute a = e.Attribute(attributeName);
+ if (a != null)
+ return a.Value;
+ else
+ throw new TimberWinR.ConfigurationErrors.MissingRequiredAttributeException(e, attributeName);
+ }
+
+ protected static string ParseStringAttribute(XElement e, string attributeName, string defaultValue = "")
+ {
+ string retValue = defaultValue;
+ XAttribute a = e.Attribute(attributeName);
+ if (a != null)
+ retValue = a.Value;
+ return retValue;
+ }
+
+ protected static bool ParseRequiredBoolAttribute(XElement e, string attributeName)
+ {
+ XAttribute a = e.Attribute(attributeName);
+ if (a == null)
+ throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(e.Attribute(attributeName));
+
+ bool retValue = false;
+ switch (a.Value)
+ {
+ case "ON":
+ case "true":
+ return true;
+
+ case "OFF":
+ case "false":
+ return false;
+ break;
+
+ default:
+ throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(e.Attribute(attributeName));
+ }
+ }
+
+ protected static string ParseEnumAttribute(XElement e, string attributeName, IEnumerable values, string defaultValue)
+ {
+ XAttribute a = e.Attribute(attributeName);
+
+ if (a != null)
+ {
+ string v = a.Value;
+ if (values.Contains(v))
+ return v;
+ else
+ throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(e.Attribute(attributeName));
+ }
+ return defaultValue;
+ }
+
+ protected static int ParseIntAttribute(XElement e, string attributeName, int defaultValue)
+ {
+ XAttribute a = e.Attribute(attributeName);
+ if (a != null)
+ {
+ int valInt;
+ if (int.TryParse(a.Value, out valInt))
+ return valInt;
+ else
+ throw new TimberWinR.ConfigurationErrors.InvalidAttributeIntegerValueException(a);
+ }
+ return defaultValue;
+ }
+ protected static bool ParseBoolAttribute(XElement e, string attributeName, bool defaultValue)
+ {
+ bool retValue = defaultValue;
+ XAttribute a = e.Attribute(attributeName);
+
+ if (a != null)
+ {
+ switch (a.Value)
+ {
+ case "ON":
+ case "true":
+ retValue = true;
+ break;
+
+ case "OFF":
+ case "false":
+ retValue = false;
+ break;
+ }
+ }
+ return retValue;
+ }
}
}
diff --git a/TimberWinR/Inputs/TailFileInput.cs b/TimberWinR/Inputs/TailFileInput.cs
index 5e62eca..700692e 100644
--- a/TimberWinR/Inputs/TailFileInput.cs
+++ b/TimberWinR/Inputs/TailFileInput.cs
@@ -14,8 +14,7 @@ namespace TimberWinR.Inputs
// Parameters
public int ICodepage { get; private set; }
public int Recurse { get; private set; }
- public bool SplitLongLines { get; private set; }
- public string ICheckpoint { get; private set; }
+ public bool SplitLongLines { get; private set; }
public static void Parse(List logs, XElement logElement)
{
@@ -29,139 +28,13 @@ namespace TimberWinR.Inputs
public TailFileInput(XElement parent)
{
- ParseName(parent);
- ParseLocation(parent);
-
- // Default values for parameters.
- ICodepage = 0;
- Recurse = 0;
- SplitLongLines = false;
-
- ParseICodepage(parent);
- ParseRecurse(parent);
- ParseSplitLongLines(parent);
- ParseICheckpoint(parent);
-
+ Name = ParseRequiredStringAttribute(parent, "name");
+ Location = ParseRequiredStringAttribute(parent, "location");
+ ICodepage = ParseIntAttribute(parent, "iCodepage", 0);
+ Recurse = ParseIntAttribute(parent, "recurse", 0);
+ SplitLongLines = ParseBoolAttribute(parent, "splitLongLines", false);
ParseFields(parent);
- }
-
- private void ParseName(XElement parent)
- {
- string attributeName = "name";
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- Name = a.Value;
- }
- catch
- {
- throw new TimberWinR.ConfigurationErrors.MissingRequiredAttributeException(parent, attributeName);
- }
- }
-
- private void ParseLocation(XElement parent)
- {
- string attributeName = "location";
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- Location = a.Value;
- }
- catch
- {
- throw new TimberWinR.ConfigurationErrors.MissingRequiredAttributeException(parent, attributeName);
- }
- }
-
- private void ParseICodepage(XElement parent)
- {
- string attributeName = "iCodepage";
- int valInt;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- if (int.TryParse(a.Value, out valInt))
- {
- ICodepage = valInt;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeIntegerValueException(a);
- }
- }
- catch
- {
- }
- }
-
- private void ParseRecurse(XElement parent)
- {
- string attributeName = "recurse";
- int valInt;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- if (int.TryParse(a.Value, out valInt))
- {
- Recurse = valInt;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeIntegerValueException(a);
- }
- }
- catch
- {
- }
- }
-
- private void ParseSplitLongLines(XElement parent)
- {
- string attributeName = "splitLongLines";
- string value;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- value = a.Value;
-
- if (value == "ON" || value == "true")
- {
- SplitLongLines = true;
- }
- else if (value == "OFF" || value == "false")
- {
- SplitLongLines = false;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(parent.Attribute(attributeName));
- }
- }
- catch { }
- }
-
- private void ParseICheckpoint(XElement parent)
- {
- string attributeName = "iCheckpoint";
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- ICheckpoint = a.Value;
- }
- catch { }
- }
+ }
private void ParseFields(XElement parent)
{
@@ -182,15 +55,12 @@ namespace TimberWinR.Inputs
sb.Append(String.Format("Name: {0}\n", Name));
sb.Append(String.Format("Location: {0}\n", Location));
sb.Append("Fields:\n");
- foreach (FieldDefinition f in Fields)
- {
- sb.Append(String.Format("\t{0}\n", f.Name));
- }
+ foreach (FieldDefinition f in Fields)
+ sb.Append(String.Format("\t{0}\n", f.Name));
sb.Append("Parameters:\n");
sb.Append(String.Format("\tiCodepage: {0}\n", ICodepage));
sb.Append(String.Format("\trecurse: {0}\n", Recurse));
- sb.Append(String.Format("\tsplitLongLines: {0}\n", SplitLongLines));
- sb.Append(String.Format("\tiCheckpoint: {0}\n", ICheckpoint));
+ sb.Append(String.Format("\tsplitLongLines: {0}\n", SplitLongLines));
return sb.ToString();
}
diff --git a/TimberWinR/Inputs/WindowsEvent.cs b/TimberWinR/Inputs/WindowsEvent.cs
index 1822823..d0f0c6f 100644
--- a/TimberWinR/Inputs/WindowsEvent.cs
+++ b/TimberWinR/Inputs/WindowsEvent.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
+using Microsoft.SqlServer.Server;
namespace TimberWinR.Inputs
{
@@ -33,237 +34,18 @@ namespace TimberWinR.Inputs
WindowsEvent(XElement parent)
{
-
- ParseSource(parent);
-
- // Default values for parameters.
- FullText = true;
- ResolveSIDS = true;
- FormatMsg = true;
- MsgErrorMode = "MSG";
- FullEventCode = false;
- Direction = "FW";
- StringsSep = "|";
- BinaryFormat = "PRINT";
-
- ParseFullText(parent);
- ParseResolveSIDS(parent);
- ParseFormatMsg(parent);
- ParseMsgErrorMode(parent);
- ParseFullEventCode(parent);
- ParseDirection(parent);
- ParseStringsSep(parent);
- ParseBinaryFormat(parent);
-
+ Source = ParseRequiredStringAttribute(parent, "source");
+ FullText = ParseBoolAttribute(parent, "fullText", true);
+ ResolveSIDS = ParseBoolAttribute(parent, "resolveSIDS", true);
+ FormatMsg = ParseBoolAttribute(parent, "formatMsg", true);
+ MsgErrorMode = ParseEnumAttribute(parent, "msgErrorMode", new string[] {"NULL", "ERROR", "MSG"}, "MSG");
+ FullEventCode = ParseBoolAttribute(parent, "fullEventCode", false); ;
+ Direction = ParseEnumAttribute(parent, "direction", new string[] { "FW", "BW" }, "FW");
+ StringsSep = ParseStringAttribute(parent, "stringsSep", "|");
+ BinaryFormat = ParseEnumAttribute(parent, "binaryFormat", new string[] { "ASC", "PRINT", "HEX" }, "PRINT");
ParseFields(parent);
}
-
- private void ParseSource(XElement parent)
- {
- string attributeName = "source";
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- Source = a.Value;
- }
- catch
- {
- throw new TimberWinR.ConfigurationErrors.MissingRequiredAttributeException(parent, attributeName);
- }
- }
-
- private void ParseFullText(XElement parent)
- {
- string attributeName = "fullText";
- string value;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- value = a.Value;
-
- if (value == "ON" || value == "true")
- {
- FullText = true;
- }
- else if (value == "OFF" || value == "false")
- {
- FullText = false;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(parent.Attribute(attributeName));
- }
- }
- catch { }
- }
-
- private void ParseResolveSIDS(XElement parent)
- {
- string attributeName = "resolveSIDS";
- string value;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- value = a.Value;
-
- if (value == "ON" || value == "true")
- {
- ResolveSIDS = true;
- }
- else if (value == "OFF" || value == "false")
- {
- ResolveSIDS = false;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(parent.Attribute(attributeName));
- }
- }
- catch { }
- }
-
- private void ParseFormatMsg(XElement parent)
- {
- string attributeName = "formatMsg";
- string value;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- value = a.Value;
-
- if (value == "ON" || value == "true")
- {
- FormatMsg = true;
- }
- else if (value == "OFF" || value == "false")
- {
- FormatMsg = false;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(parent.Attribute(attributeName));
- }
- }
- catch { }
- }
-
- private void ParseMsgErrorMode(XElement parent)
- {
- string attributeName = "msgErrorMode";
- string value;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- value = a.Value;
-
- if (value == "NULL" || value == "ERROR" || value == "MSG")
- {
- MsgErrorMode = value;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(parent.Attribute(attributeName));
- }
- }
- catch { }
- }
-
- private void ParseFullEventCode(XElement parent)
- {
- string attributeName = "fullEventCode";
- string value;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- value = a.Value;
-
- if (value == "ON" || value == "true")
- {
- FullEventCode = true;
- }
- else if (value == "OFF" || value == "false")
- {
- FullEventCode = false;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(parent.Attribute(attributeName));
- }
- }
- catch { }
- }
-
- private void ParseDirection(XElement parent)
- {
- string attributeName = "direction";
- string value;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- value = a.Value;
-
- if (value == "FW" || value == "BW")
- {
- Direction = value;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(parent.Attribute(attributeName));
- }
- }
- catch { }
- }
-
- private void ParseStringsSep(XElement parent)
- {
- string attributeName = "stringsSep";
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- StringsSep = a.Value;
- }
- catch { }
- }
-
- private void ParseBinaryFormat(XElement parent)
- {
- string attributeName = "binaryFormat";
- string value;
-
- try
- {
- XAttribute a = parent.Attribute(attributeName);
-
- value = a.Value;
-
- if (value == "ASC" || value == "PRINT" || value == "HEX")
- {
- BinaryFormat = value;
- }
- else
- {
- throw new TimberWinR.ConfigurationErrors.InvalidAttributeValueException(parent.Attribute(attributeName));
- }
- }
- catch { }
- }
-
+
private void ParseFields(XElement parent)
{
Dictionary allPossibleFields = new Dictionary()
diff --git a/TimberWinR/configSchema.xsd b/TimberWinR/configSchema.xsd
index cd4bd01..9059af2 100644
--- a/TimberWinR/configSchema.xsd
+++ b/TimberWinR/configSchema.xsd
@@ -4,7 +4,8 @@
-
+
+
@@ -247,56 +248,96 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+