From 86451938ec6bfac4ad9c69e183470f989814decc Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 26 Apr 2014 11:18:56 +0200 Subject: [PATCH] Initial commit of Ocean's local development --- Configuration/Doc.go | 19 +++ Configuration/Init.go | 6 + Configuration/Meta/Structure.go | 10 ++ Configuration/PublicRead.go | 14 ++ Configuration/ReadConfiguration.go | 52 ++++++ Configuration/Variables.go | 11 ++ ConfigurationDB/CheckConfiguration.go | 65 ++++++++ ConfigurationDB/Doc.go | 10 ++ ConfigurationDB/Init.go | 38 +++++ ConfigurationDB/Read.go | 24 +++ ConfigurationDB/Scheme.go | 6 + ConfigurationDB/Shutdown.go | 19 +++ ConfigurationDB/Variables.go | 13 ++ CustomerDB/AccessDB.go | 19 +++ CustomerDB/Doc.go | 5 + CustomerDB/Init.go | 52 ++++++ CustomerDB/Shutdown.go | 19 +++ CustomerDB/Variables.go | 11 ++ ICCC/Data2Message.go | 122 +++++++++++++++ ICCC/Doc.go | 4 + ICCC/HTTPConnector.go | 34 ++++ ICCC/Init.go | 25 +++ ICCC/InitDB.go | 58 +++++++ ICCC/ListenerCache.go | 41 +++++ ICCC/Message2Data.go | 95 +++++++++++ ICCC/Register2Database.go | 47 ++++++ ICCC/RegisterHost2Database.go | 26 +++ ICCC/Registrar.go | 15 ++ ICCC/Scheme/Host.go | 6 + ICCC/Scheme/Listener.go | 8 + ICCC/Send.go | 19 +++ ICCC/Shutdown.go | 30 ++++ ICCC/Variables.go | 27 ++++ ICCC/WriteMessage2All.go | 26 +++ ICCC/WriteMessage2Any.go | 31 ++++ Log/AddDevice.go | 19 +++ Log/Array.go | 16 ++ Log/Device/Device.go | 8 + Log/Device/Doc.go | 4 + Log/DeviceConsole/ActivateLoggingDevice.go | 7 + Log/DeviceConsole/Console.go | 17 ++ Log/DeviceConsole/Doc.go | 4 + Log/DeviceDatabase/ActivateLoggingDevice.go | 7 + Log/DeviceDatabase/Doc.go | 5 + Log/DeviceDatabase/Flush.go | 14 ++ Log/DeviceDatabase/Init.go | 31 ++++ Log/DeviceDatabase/InitDB.go | 165 ++++++++++++++++++++ Log/DeviceDatabase/ReadFromCache.go | 39 +++++ Log/DeviceDatabase/ReceiveLogging.go | 15 ++ Log/DeviceDatabase/Scheme.go | 16 ++ Log/DeviceDatabase/Variables.go | 16 ++ Log/DeviceDatabase/Write2Cache.go | 24 +++ Log/DeviceDatabase/Write2Database.go | 7 + Log/DeviceDelay.go | 43 +++++ Log/Flush.go | 33 ++++ Log/Handlers.go | 134 ++++++++++++++++ Log/Init.go | 52 ++++++ Log/LoggingIsReady.go | 9 ++ Log/Meta/Category.go | 27 ++++ Log/Meta/Entry.go | 16 ++ Log/Meta/Format.go | 96 ++++++++++++ Log/Meta/Impact.go | 33 ++++ Log/Meta/Level.go | 33 ++++ Log/Meta/MessageNames.go | 34 ++++ Log/Meta/Sender.go | 3 + Log/Meta/Severity.go | 33 ++++ Log/Scheduler.go | 38 +++++ Log/SetConfiguration.go | 61 ++++++++ Log/Timer.go | 37 +++++ Log/Variables.go | 25 +++ MimeTypes/Init.go | 38 +++++ MimeTypes/KnownTypes.go | 34 ++++ MimeTypes/MimeTypes.go | 22 +++ MimeTypes/Write2HTTP.go | 7 + NumGen/BadNumber.go | 6 + NumGen/GetNumber.go | 30 ++++ NumGen/HandlerGetNext.go | 41 +++++ NumGen/Init.go | 38 +++++ NumGen/InitDB.go | 28 ++++ NumGen/Producer.go | 68 ++++++++ NumGen/RequestChannel.go | 38 +++++ NumGen/Scheme.go | 6 + NumGen/Shutdown.go | 11 ++ NumGen/Variables.go | 22 +++ Robots/Handler.go | 17 ++ Robots/Init.go | 10 ++ Robots/Variables.go | 9 ++ Shutdown/Check.go | 6 + Shutdown/Init.go | 13 ++ Shutdown/Shutdown.go | 29 ++++ Shutdown/Variables.go | 12 ++ StaticFiles/FindAndReadFile.go | 47 ++++++ StaticFiles/Handler.go | 57 +++++++ StaticFiles/Init.go | 44 ++++++ StaticFiles/Map2Root.go | 19 +++ StaticFiles/Variables.go | 10 ++ System/ICCCStart.go | 8 + System/Init.go | 90 +++++++++++ System/InitHandlers.go | 30 ++++ System/RegisterLoggingDevices.go | 35 +++++ System/Start.go | 18 +++ System/Variables.go | 7 + Templates/Init.go | 59 +++++++ Templates/Process.go | 19 +++ Templates/Variables.go | 11 ++ Tools/Hostname.go | 6 + Tools/IPAddresses.go | 42 +++++ Tools/Init.go | 27 ++++ Tools/InternalCommPassword.go | 6 + Tools/Random.go | 8 + Tools/Variables.go | 10 ++ WebContent/GetContent.go | 43 +++++ WebContent/Handler.go | 22 +++ WebContent/Init.go | 36 +++++ WebContent/Send.go | 43 +++++ WebContent/Variables.go | 10 ++ 116 files changed, 3320 insertions(+) create mode 100644 Configuration/Doc.go create mode 100644 Configuration/Init.go create mode 100644 Configuration/Meta/Structure.go create mode 100644 Configuration/PublicRead.go create mode 100644 Configuration/ReadConfiguration.go create mode 100644 Configuration/Variables.go create mode 100644 ConfigurationDB/CheckConfiguration.go create mode 100644 ConfigurationDB/Doc.go create mode 100644 ConfigurationDB/Init.go create mode 100644 ConfigurationDB/Read.go create mode 100644 ConfigurationDB/Scheme.go create mode 100644 ConfigurationDB/Shutdown.go create mode 100644 ConfigurationDB/Variables.go create mode 100644 CustomerDB/AccessDB.go create mode 100644 CustomerDB/Doc.go create mode 100644 CustomerDB/Init.go create mode 100644 CustomerDB/Shutdown.go create mode 100644 CustomerDB/Variables.go create mode 100644 ICCC/Data2Message.go create mode 100644 ICCC/Doc.go create mode 100644 ICCC/HTTPConnector.go create mode 100644 ICCC/Init.go create mode 100644 ICCC/InitDB.go create mode 100644 ICCC/ListenerCache.go create mode 100644 ICCC/Message2Data.go create mode 100644 ICCC/Register2Database.go create mode 100644 ICCC/RegisterHost2Database.go create mode 100644 ICCC/Registrar.go create mode 100644 ICCC/Scheme/Host.go create mode 100644 ICCC/Scheme/Listener.go create mode 100644 ICCC/Send.go create mode 100644 ICCC/Shutdown.go create mode 100644 ICCC/Variables.go create mode 100644 ICCC/WriteMessage2All.go create mode 100644 ICCC/WriteMessage2Any.go create mode 100644 Log/AddDevice.go create mode 100644 Log/Array.go create mode 100644 Log/Device/Device.go create mode 100644 Log/Device/Doc.go create mode 100644 Log/DeviceConsole/ActivateLoggingDevice.go create mode 100644 Log/DeviceConsole/Console.go create mode 100644 Log/DeviceConsole/Doc.go create mode 100644 Log/DeviceDatabase/ActivateLoggingDevice.go create mode 100644 Log/DeviceDatabase/Doc.go create mode 100644 Log/DeviceDatabase/Flush.go create mode 100644 Log/DeviceDatabase/Init.go create mode 100644 Log/DeviceDatabase/InitDB.go create mode 100644 Log/DeviceDatabase/ReadFromCache.go create mode 100644 Log/DeviceDatabase/ReceiveLogging.go create mode 100644 Log/DeviceDatabase/Scheme.go create mode 100644 Log/DeviceDatabase/Variables.go create mode 100644 Log/DeviceDatabase/Write2Cache.go create mode 100644 Log/DeviceDatabase/Write2Database.go create mode 100644 Log/DeviceDelay.go create mode 100644 Log/Flush.go create mode 100644 Log/Handlers.go create mode 100644 Log/Init.go create mode 100644 Log/LoggingIsReady.go create mode 100644 Log/Meta/Category.go create mode 100644 Log/Meta/Entry.go create mode 100644 Log/Meta/Format.go create mode 100644 Log/Meta/Impact.go create mode 100644 Log/Meta/Level.go create mode 100644 Log/Meta/MessageNames.go create mode 100644 Log/Meta/Sender.go create mode 100644 Log/Meta/Severity.go create mode 100644 Log/Scheduler.go create mode 100644 Log/SetConfiguration.go create mode 100644 Log/Timer.go create mode 100644 Log/Variables.go create mode 100644 MimeTypes/Init.go create mode 100644 MimeTypes/KnownTypes.go create mode 100644 MimeTypes/MimeTypes.go create mode 100644 MimeTypes/Write2HTTP.go create mode 100644 NumGen/BadNumber.go create mode 100644 NumGen/GetNumber.go create mode 100644 NumGen/HandlerGetNext.go create mode 100644 NumGen/Init.go create mode 100644 NumGen/InitDB.go create mode 100644 NumGen/Producer.go create mode 100644 NumGen/RequestChannel.go create mode 100644 NumGen/Scheme.go create mode 100644 NumGen/Shutdown.go create mode 100644 NumGen/Variables.go create mode 100644 Robots/Handler.go create mode 100644 Robots/Init.go create mode 100644 Robots/Variables.go create mode 100644 Shutdown/Check.go create mode 100644 Shutdown/Init.go create mode 100644 Shutdown/Shutdown.go create mode 100644 Shutdown/Variables.go create mode 100644 StaticFiles/FindAndReadFile.go create mode 100644 StaticFiles/Handler.go create mode 100644 StaticFiles/Init.go create mode 100644 StaticFiles/Map2Root.go create mode 100644 StaticFiles/Variables.go create mode 100644 System/ICCCStart.go create mode 100644 System/Init.go create mode 100644 System/InitHandlers.go create mode 100644 System/RegisterLoggingDevices.go create mode 100644 System/Start.go create mode 100644 System/Variables.go create mode 100644 Templates/Init.go create mode 100644 Templates/Process.go create mode 100644 Templates/Variables.go create mode 100644 Tools/Hostname.go create mode 100644 Tools/IPAddresses.go create mode 100644 Tools/Init.go create mode 100644 Tools/InternalCommPassword.go create mode 100644 Tools/Random.go create mode 100644 Tools/Variables.go create mode 100644 WebContent/GetContent.go create mode 100644 WebContent/Handler.go create mode 100644 WebContent/Init.go create mode 100644 WebContent/Send.go create mode 100644 WebContent/Variables.go diff --git a/Configuration/Doc.go b/Configuration/Doc.go new file mode 100644 index 0000000..4477d53 --- /dev/null +++ b/Configuration/Doc.go @@ -0,0 +1,19 @@ +/* +This package reads the configuration file from the disk (file: configuration.json). +This configuratin is just used to specific the configuration database to go further. + +An example configuration.json file: + + { + "ConfigDBHostname" : "localhost:27017", + "ConfigDBDatabase" : "MyDatabase", + "ConfigDBConfigurationCollection" : "Configuration", + "ConfigDBConfigurationCollectionUsername" : "ConfigurationUsername", + "ConfigDBConfigurationCollectionPassword" : "ConfigurationPassword" + } + +Hint #1: Ocean is using MongoDB as database ;-) +Hint #2: Normally, you do not use this package at all, because the application configuration should persist +inside the configuration database. Your task: Provide a configuration.json file at the installation directory ;-) +*/ +package Configuration diff --git a/Configuration/Init.go b/Configuration/Init.go new file mode 100644 index 0000000..9682b9a --- /dev/null +++ b/Configuration/Init.go @@ -0,0 +1,6 @@ +package Configuration + +func init() { + readConfiguration() + isInit = true +} diff --git a/Configuration/Meta/Structure.go b/Configuration/Meta/Structure.go new file mode 100644 index 0000000..04e83cb --- /dev/null +++ b/Configuration/Meta/Structure.go @@ -0,0 +1,10 @@ +package Meta + +// The type which matches the configuration file: +type Configuration struct { + ConfigDBHostname string + ConfigDBDatabase string + ConfigDBConfigurationCollection string + ConfigDBConfigurationCollectionUsername string + ConfigDBConfigurationCollectionPassword string +} diff --git a/Configuration/PublicRead.go b/Configuration/PublicRead.go new file mode 100644 index 0000000..2e4b736 --- /dev/null +++ b/Configuration/PublicRead.go @@ -0,0 +1,14 @@ +package Configuration + +import "github.com/SommerEngineering/Ocean/Configuration/Meta" + +/* +Read the whole configuration and enable Ocean to get the configuration database. + +Hint: Normally, you do not use this package at all, because the application configuration should persist +inside the configuration database. +*/ +func Read() (result Meta.Configuration) { + result = configuration + return +} diff --git a/Configuration/ReadConfiguration.go b/Configuration/ReadConfiguration.go new file mode 100644 index 0000000..e791bfe --- /dev/null +++ b/Configuration/ReadConfiguration.go @@ -0,0 +1,52 @@ +package Configuration + +import "encoding/json" +import "os" +import "path/filepath" +import "github.com/SommerEngineering/Ocean/Log" +import "github.com/SommerEngineering/Ocean/Log/Meta" + +func readConfiguration() { + + if isInit { + Log.LogFull(senderName, Meta.CategorySYSTEM, Meta.LevelWARN, Meta.SeverityNone, Meta.ImpactNone, Meta.MessageNameINIT, `The configuration package is already init!`) + return + } else { + Log.LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, Meta.MessageNameCONFIGURATION, `Init of configuration starting.`) + } + + currentDir, dirError := os.Getwd() + + if dirError != nil { + panic(`Was not able to read the working directory: ` + dirError.Error()) + return + } + + currentPath := filepath.Join(currentDir, filename) + if _, errFile := os.Stat(currentPath); errFile != nil { + if os.IsNotExist(errFile) { + panic(`It was not possible to find the necessary configuration file 'configuration.json' at the application directory.`) + } else { + panic(`There was an error while open the configuration: ` + errFile.Error()) + } + } + + file, fileError := os.Open(currentPath) + defer file.Close() + + if fileError != nil { + panic(`The configuration file is not accessible: ` + fileError.Error()) + return + } + + decoder := json.NewDecoder(file) + decError := decoder.Decode(&configuration) + + if decError != nil { + panic(`Decoding of the configuration file was not possible: ` + decError.Error()) + } + + Log.LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, Meta.MessageNameINIT, `Init of configuration is done.`) + isInit = true + return +} diff --git a/Configuration/Variables.go b/Configuration/Variables.go new file mode 100644 index 0000000..871a9d0 --- /dev/null +++ b/Configuration/Variables.go @@ -0,0 +1,11 @@ +package Configuration + +import "github.com/SommerEngineering/Ocean/Configuration/Meta" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + filename = "configuration.json" // Where is the configuration located? + configuration Meta.Configuration = Meta.Configuration{} // The loaded configuration + isInit = false // Is the configuration loaded? + senderName LM.Sender = `System::Configuration` +) diff --git a/ConfigurationDB/CheckConfiguration.go b/ConfigurationDB/CheckConfiguration.go new file mode 100644 index 0000000..c3b340c --- /dev/null +++ b/ConfigurationDB/CheckConfiguration.go @@ -0,0 +1,65 @@ +package ConfigurationDB + +import "labix.org/v2/mgo/bson" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func checkConfiguration() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Check now the configuration database.`) + defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Done checking the configuration database.`) + + CheckSingleConfigurationPresentsAndAddIfMissing(`InternalCommPassword`, `please replace this with e.g. a random GUID, etc.`) + CheckSingleConfigurationPresentsAndAddIfMissing(`CustomerDBHost`, `localhost:27017`) + CheckSingleConfigurationPresentsAndAddIfMissing(`CustomerDBDatabase`, `Ocean`) + CheckSingleConfigurationPresentsAndAddIfMissing(`CustomerDBUsername`, `root`) + CheckSingleConfigurationPresentsAndAddIfMissing(`CustomerDBPassword`, `please replace this with a good password`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogDBHost`, `localhost:27017`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogDBDatabase`, `Ocean`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogDBUsername`, `root`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogDBPassword`, `please replace this with a good password`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogDBCacheSizeNumberOfEvents`, `50`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogDBCacheSizeTime2FlushSeconds`, `6`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogBufferSize`, `500`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogDeviceDelayNumberEvents`, `600`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogDeviceDelayTime2FlushSeconds`, `5`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogTimeoutSeconds`, `4`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogStaticFileRequests`, `false`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogUseDatabaseLogging`, `false`) + CheckSingleConfigurationPresentsAndAddIfMissing(`LogUseConsoleLogging`, `true`) + CheckSingleConfigurationPresentsAndAddIfMissing(`NumGenActiveHosts`, `please replace this with the correct hostname of the host which is the master number generator`) + CheckSingleConfigurationPresentsAndAddIfMissing(`NumGenGetHandler`, `http://localhost:80/next/number`) + CheckSingleConfigurationPresentsAndAddIfMissing(`NumGenBufferSize`, `12`) + CheckSingleConfigurationPresentsAndAddIfMissing(`OceanHostnameAndPort`, `:60000`) + CheckSingleConfigurationPresentsAndAddIfMissing(`OceanUtilizeCPUs`, `2`) + CheckSingleConfigurationPresentsAndAddIfMissing(`FilenameWebResources`, `web.zip`) + CheckSingleConfigurationPresentsAndAddIfMissing(`MapStaticFiles2Root`, `false`) + CheckSingleConfigurationPresentsAndAddIfMissing(`MapStaticFiles2RootRootFile`, `index.html`) + CheckSingleConfigurationPresentsAndAddIfMissing(`EnableStaticFiles`, `true`) + CheckSingleConfigurationPresentsAndAddIfMissing(`robots.txt`, `User-agent: * +Disallow:`) +} + +/* +Use this function to ensure that the database contains at least a default value for the configuration. +*/ +func CheckSingleConfigurationPresentsAndAddIfMissing(name, value string) { + if !checkSingleConfigurationPresents(name) { + addSingleConfiguration(name, value) + } +} + +func checkSingleConfigurationPresents(name string) (result bool) { + selection := bson.D{{"Name", name}} + count, _ := collection.Find(selection).Count() + + return count > 0 +} + +func addSingleConfiguration(name, value string) { + entry := ConfigurationDBEntry{} + entry.Name = name + entry.Value = value + collection.Insert(entry) + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Add a missing configuration to the configuration database.`, `Name=`+name, `Value=`+value) +} diff --git a/ConfigurationDB/Doc.go b/ConfigurationDB/Doc.go new file mode 100644 index 0000000..6c443f5 --- /dev/null +++ b/ConfigurationDB/Doc.go @@ -0,0 +1,10 @@ +/* +This package provides the configuration database access: Ocean uses MongoDB as database! The configuration is represented as +name value pairs. Both, the name and the value, are strings. If you need numbers for the configuration, you have to convert +these strings in to numbers afterwards. Use the Read() function to read a specific configuration value. + +To provide own application configurations, use the function CheckSingleConfigurationPresentsAndAddIfMissing() to ensure, that +at least a default value is present in the database. Collect all these function calls inside a init() function somewhere at your +application. +*/ +package ConfigurationDB diff --git a/ConfigurationDB/Init.go b/ConfigurationDB/Init.go new file mode 100644 index 0000000..2722111 --- /dev/null +++ b/ConfigurationDB/Init.go @@ -0,0 +1,38 @@ +package ConfigurationDB + +import "labix.org/v2/mgo" +import "github.com/SommerEngineering/Ocean/Configuration" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func init() { + + config := Configuration.Read() + + // Connect to MongoDB: + if newSession, errDial := mgo.Dial(config.ConfigDBHostname); errDial != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to connect to the MongoDB host `+config.ConfigDBHostname, errDial.Error()) + return + } else { + session = newSession + } + + // Use the correct database: + db = session.DB(config.ConfigDBDatabase) + + // Login: + if errLogin := db.Login(config.ConfigDBConfigurationCollectionUsername, config.ConfigDBConfigurationCollectionPassword); errLogin != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to login the user `+config.ConfigDBConfigurationCollectionUsername, errLogin.Error()) + return + } + + // Get the collection: + collection = db.C(config.ConfigDBConfigurationCollection) + + // Take care about the index: + collection.EnsureIndexKey(`Name`) + collection.EnsureIndexKey(`Value`) + + checkConfiguration() + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `The configuration database is now ready.`) +} diff --git a/ConfigurationDB/Read.go b/ConfigurationDB/Read.go new file mode 100644 index 0000000..735c2da --- /dev/null +++ b/ConfigurationDB/Read.go @@ -0,0 +1,24 @@ +package ConfigurationDB + +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" +import "labix.org/v2/mgo/bson" + +/* +This function reads the current configuration value. +*/ +func Read(name string) (value string) { + if name == `` { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to read a configuration out of the database.`, `The given name was nil!`) + return + } + + result := ConfigurationDBEntry{} + if errFind := collection.Find(bson.D{{"Name", name}}).One(&result); errFind != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to read a configuration out of the database.`, `Error while find.`, errFind.Error()) + return + } + + value = result.Value + return +} diff --git a/ConfigurationDB/Scheme.go b/ConfigurationDB/Scheme.go new file mode 100644 index 0000000..fa02b6a --- /dev/null +++ b/ConfigurationDB/Scheme.go @@ -0,0 +1,6 @@ +package ConfigurationDB + +type ConfigurationDBEntry struct { + Name string `bson:"Name"` + Value string `bson:"Value"` +} diff --git a/ConfigurationDB/Shutdown.go b/ConfigurationDB/Shutdown.go new file mode 100644 index 0000000..0f84581 --- /dev/null +++ b/ConfigurationDB/Shutdown.go @@ -0,0 +1,19 @@ +package ConfigurationDB + +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +/* +Do not use this type by your own! It is a Ocean internal type to provide a shutdown function for the configuration database. +*/ +type ShutdownFunction struct { +} + +/* +If the Ocean server is shutting down, this function is called to close the database. +*/ +func (a ShutdownFunction) Shutdown() { + Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Close now the configuration database connection.`) + db.Logout() + session.Close() +} diff --git a/ConfigurationDB/Variables.go b/ConfigurationDB/Variables.go new file mode 100644 index 0000000..cc578a3 --- /dev/null +++ b/ConfigurationDB/Variables.go @@ -0,0 +1,13 @@ +package ConfigurationDB + +import "labix.org/v2/mgo" +import "github.com/SommerEngineering/Ocean/Configuration/Meta" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + session *mgo.Session = nil + db *mgo.Database = nil + collection *mgo.Collection = nil + config Meta.Configuration = Meta.Configuration{} + senderName LM.Sender = `System::ConfigurationDB` +) diff --git a/CustomerDB/AccessDB.go b/CustomerDB/AccessDB.go new file mode 100644 index 0000000..5db44aa --- /dev/null +++ b/CustomerDB/AccessDB.go @@ -0,0 +1,19 @@ +package CustomerDB + +import "labix.org/v2/mgo" + +/* +Get the database instance of the MGo Mongo driver. +*/ +func DB() (result *mgo.Database) { + result = db + return +} + +/* +Get directly the GridFS instance of the Mgo Mongo driver. +*/ +func GridFS() (result *mgo.GridFS) { + result = gridFS + return +} diff --git a/CustomerDB/Doc.go b/CustomerDB/Doc.go new file mode 100644 index 0000000..96e70e6 --- /dev/null +++ b/CustomerDB/Doc.go @@ -0,0 +1,5 @@ +/* +This package provides access to the customer database. To specific the customer database, please use the configuration database. +By the way: Ocean uses MongoDB as database ;-) This package is just a facade to hide the complexity from MongoDB and its driver. +*/ +package CustomerDB diff --git a/CustomerDB/Init.go b/CustomerDB/Init.go new file mode 100644 index 0000000..e3df9df --- /dev/null +++ b/CustomerDB/Init.go @@ -0,0 +1,52 @@ +package CustomerDB + +import "labix.org/v2/mgo" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func init() { + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Init the customer database.`) + + databaseHost := ConfigurationDB.Read(`CustomerDBHost`) + databaseDB := ConfigurationDB.Read(`CustomerDBDatabase`) + databaseUsername := ConfigurationDB.Read(`CustomerDBUsername`) + databasePassword := ConfigurationDB.Read(`CustomerDBPassword`) + + // Connect to MongoDB: + if newSession, errDial := mgo.Dial(databaseHost); errDial != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to connect to the MongoDB host `+databaseHost, errDial.Error()) + return + } else { + session = newSession + } + + // Use the correct database: + db = session.DB(databaseDB) + if db == nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to get the customer database.`) + return + } + + // Login: + if errLogin := db.Login(databaseUsername, databasePassword); errLogin != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to login the user `+databaseUsername, errLogin.Error()) + return + } + + // Get the GridFS: + gridFS = db.GridFS(`fs`) + if gridFS == nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to get the GridFS from the database.`) + return + } + + // Ensure the indexes for the GridFS: + filesCollection := gridFS.Files + filesCollection.EnsureIndexKey(`uploadDate`) + filesCollection.EnsureIndexKey(`filename`) + filesCollection.EnsureIndexKey(`filename`, `uploadDate`) + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameDATABASE, `Customer database is now ready.`) +} diff --git a/CustomerDB/Shutdown.go b/CustomerDB/Shutdown.go new file mode 100644 index 0000000..9ed25b8 --- /dev/null +++ b/CustomerDB/Shutdown.go @@ -0,0 +1,19 @@ +package CustomerDB + +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +/* +Please do not use this type. It is an internal type of Ocean to provide a shutdown function! +*/ +type ShutdownFunction struct { +} + +/* +This function is called if the Ocean server is shutting down. +*/ +func (a ShutdownFunction) Shutdown() { + Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Close now the customer database connection.`) + db.Logout() + session.Close() +} diff --git a/CustomerDB/Variables.go b/CustomerDB/Variables.go new file mode 100644 index 0000000..73a713a --- /dev/null +++ b/CustomerDB/Variables.go @@ -0,0 +1,11 @@ +package CustomerDB + +import "labix.org/v2/mgo" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + session *mgo.Session = nil + db *mgo.Database = nil + gridFS *mgo.GridFS = nil + senderName LM.Sender = `System::CustomerDB` +) diff --git a/ICCC/Data2Message.go b/ICCC/Data2Message.go new file mode 100644 index 0000000..29e7cf1 --- /dev/null +++ b/ICCC/Data2Message.go @@ -0,0 +1,122 @@ +package ICCC + +import "fmt" +import "reflect" +import "strconv" + +func Data2Message(target interface{}, data map[string][]string) (channel, command string, obj interface{}) { + if data == nil || len(data) == 0 { + channel = `` + command = `` + obj = nil + return + } + + element := reflect.ValueOf(target) + element = element.Elem() + elementType := element.Type() + + channel = data[`channel`][0] + command = data[`command`][0] + for i := 0; i < element.NumField(); i++ { + field := element.Field(i) + switch field.Kind().String() { + + case `int64`: + mapName := fmt.Sprintf(`int:%s`, elementType.Field(i).Name) + mapValue := data[mapName][0] + v, _ := strconv.ParseInt(mapValue, 10, 64) + field.SetInt(v) + + case `string`: + mapName := fmt.Sprintf(`str:%s`, elementType.Field(i).Name) + mapValue := data[mapName][0] + field.SetString(mapValue) + + case `float64`: + mapName := fmt.Sprintf(`f64:%s`, elementType.Field(i).Name) + mapValue := data[mapName][0] + v, _ := strconv.ParseFloat(mapValue, 64) + field.SetFloat(v) + + case `bool`: + mapName := fmt.Sprintf(`bool:%s`, elementType.Field(i).Name) + mapValue := data[mapName][0] + v, _ := strconv.ParseBool(mapValue) + field.SetBool(v) + + case `uint8`: + mapName := fmt.Sprintf(`ui8:%s`, elementType.Field(i).Name) + mapValue := data[mapName][0] + v, _ := strconv.ParseUint(mapValue, 16, 8) + field.SetUint(v) + + case `slice`: + sliceInterface := field.Interface() + sliceKind := reflect.ValueOf(sliceInterface).Type().String() + + switch sliceKind { + case `[]uint8`: // bytes + mapName := fmt.Sprintf(`ui8[]:%s`, elementType.Field(i).Name) + mapValues := data[mapName] + fieldLen := len(mapValues) + fieldData := make([]uint8, fieldLen, fieldLen) + for n, mapValue := range mapValues { + v, _ := strconv.ParseUint(mapValue, 16, 8) + fieldData[n] = byte(v) + } + + fieldDataValue := reflect.ValueOf(fieldData) + field.Set(fieldDataValue) + + case `[]int64`: + mapName := fmt.Sprintf(`int[]:%s`, elementType.Field(i).Name) + mapValues := data[mapName] + fieldLen := len(mapValues) + fieldData := make([]int64, fieldLen, fieldLen) + for n, mapValue := range mapValues { + v, _ := strconv.ParseInt(mapValue, 10, 64) + fieldData[n] = v + } + + fieldDataValue := reflect.ValueOf(fieldData) + field.Set(fieldDataValue) + + case `[]bool`: + mapName := fmt.Sprintf(`bool[]:%s`, elementType.Field(i).Name) + mapValues := data[mapName] + fieldLen := len(mapValues) + fieldData := make([]bool, fieldLen, fieldLen) + for n, mapValue := range mapValues { + v, _ := strconv.ParseBool(mapValue) + fieldData[n] = v + } + + fieldDataValue := reflect.ValueOf(fieldData) + field.Set(fieldDataValue) + + case `[]string`: + mapName := fmt.Sprintf(`str[]:%s`, elementType.Field(i).Name) + mapValues := data[mapName] + fieldDataValue := reflect.ValueOf(mapValues) + field.Set(fieldDataValue) + + case `[]float64`: + mapName := fmt.Sprintf(`f64[]:%s`, elementType.Field(i).Name) + mapValues := data[mapName] + fieldLen := len(mapValues) + fieldData := make([]float64, fieldLen, fieldLen) + for n, mapValue := range mapValues { + v, _ := strconv.ParseFloat(mapValue, 64) + fieldData[n] = v + } + + fieldDataValue := reflect.ValueOf(fieldData) + field.Set(fieldDataValue) + } + } + } + + obj = target + return +} diff --git a/ICCC/Doc.go b/ICCC/Doc.go new file mode 100644 index 0000000..95e7e3e --- /dev/null +++ b/ICCC/Doc.go @@ -0,0 +1,4 @@ +/* +This is the "[I]nter Data [C]enter Se[c]ure [C]ommunication" :) +*/ +package ICCC diff --git a/ICCC/HTTPConnector.go b/ICCC/HTTPConnector.go new file mode 100644 index 0000000..a6fea9a --- /dev/null +++ b/ICCC/HTTPConnector.go @@ -0,0 +1,34 @@ +package ICCC + +import "fmt" +import "net/http" +import "github.com/SommerEngineering/Ocean/Tools" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func ICCCHandler(response http.ResponseWriter, request *http.Request) { + if errParse := request.ParseForm(); errParse != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNETWORK, `Was not able to parse the HTTP form data from an ICCC message!`) + http.NotFound(response, request) + return + } + + messageData := map[string][]string(request.PostForm) + channel := messageData[`channel`][0] + command := messageData[`command`][0] + password := messageData[`InternalCommPassword`][0] + + if password != Tools.InternalCommPassword() { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityCritical, LM.ImpactNone, LM.MessageNamePASSWORD, `Received a ICCC message with wrong password!`, request.RemoteAddr) + http.NotFound(response, request) + return + } + + key := fmt.Sprintf(`%s::%s`, channel, command) + listener := listeners[key] + if listener == nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to find the correct listener for these ICCC message.`, `channel=`+channel, `command`+command, `hostname=`+Tools.ThisHostname()) + } else { + listener(messageData) + } +} diff --git a/ICCC/Init.go b/ICCC/Init.go new file mode 100644 index 0000000..d1dd496 --- /dev/null +++ b/ICCC/Init.go @@ -0,0 +1,25 @@ +package ICCC + +import "strings" +import "container/list" +import "github.com/SommerEngineering/Ocean/Tools" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func init() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Start init of ICCC.`) + defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Done init ICCC.`) + + cacheListenerDatabase = list.New() + listeners = make(map[string]func(data map[string][]string)) + + allHostsIPAddresses := Tools.ReadAllIPAddresses4ThisHost() + oceanHostnameAndPort := ConfigurationDB.Read(`OceanHostnameAndPort`) + port := oceanHostnameAndPort[strings.Index(oceanHostnameAndPort, `:`):] + correctAddressWithPort = allHostsIPAddresses[0] + port + + initDB() + registerHost2Database() + initCacheTimer() +} diff --git a/ICCC/InitDB.go b/ICCC/InitDB.go new file mode 100644 index 0000000..847a391 --- /dev/null +++ b/ICCC/InitDB.go @@ -0,0 +1,58 @@ +package ICCC + +import "labix.org/v2/mgo" +import "github.com/SommerEngineering/Ocean/CustomerDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func initDB() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Start init of the ICCC collections.`) + defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Done init the ICCC collection.`) + + // Get the database: + db = CustomerDB.DB() + + if db == nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to get the customer database.`) + return + } + + // Get my collections: + collectionListener = db.C(`ICCCListener`) + collectionHosts = db.C(`ICCCHosts`) + + // Take care about the indexes for ICCCListener: + collectionListener.EnsureIndexKey(`Command`) + collectionListener.EnsureIndexKey(`Command`, `IsActive`) + + collectionListener.EnsureIndexKey(`Command`, `Channel`) + collectionListener.EnsureIndexKey(`Command`, `Channel`, `IsActive`) + + collectionListener.EnsureIndexKey(`Channel`) + collectionListener.EnsureIndexKey(`Channel`, `IsActive`) + collectionListener.EnsureIndexKey(`Channel`, `Command`, `IPAddressPort`, `IsActive`) + collectionListener.EnsureIndexKey(`Channel`, `Command`, `IsActive`) + + collectionListener.EnsureIndexKey(`IsActive`) + collectionListener.EnsureIndexKey(`IsActive`, `IPAddressPort`) + + collectionListener.EnsureIndexKey(`IPAddressPort`) + + indexName1 := mgo.Index{} + indexName1.Key = []string{`Channel`, `Command`, `IPAddressPort`} + indexName1.Unique = true + collectionListener.EnsureIndex(indexName1) + + // Index for hosts: + collectionHosts.EnsureIndexKey(`Hostname`, `IPAddressPort`) + + indexName2 := mgo.Index{} + indexName2.Key = []string{`Hostname`} + indexName2.Unique = true + collectionHosts.EnsureIndex(indexName2) + + indexName3 := mgo.Index{} + indexName3.Key = []string{`IPAddressPort`} + indexName3.Unique = true + collectionHosts.EnsureIndex(indexName3) +} diff --git a/ICCC/ListenerCache.go b/ICCC/ListenerCache.go new file mode 100644 index 0000000..19c2ce6 --- /dev/null +++ b/ICCC/ListenerCache.go @@ -0,0 +1,41 @@ +package ICCC + +import "fmt" +import "time" +import "labix.org/v2/mgo/bson" +import "github.com/SommerEngineering/Ocean/Shutdown" +import "github.com/SommerEngineering/Ocean/ICCC/Scheme" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func initCacheTimer() { + + go func() { + for { + + if Shutdown.IsDown() { + return + } + + lastCount := cacheListenerDatabase.Len() + selection := bson.D{{`IsActive`, true}} + entriesIterator := collectionListener.Find(selection).Iter() + entry := Scheme.Listener{} + + cacheListenerDatabaseLock.Lock() + cacheListenerDatabase.Init() + for entriesIterator.Next(&entry) { + cacheListenerDatabase.PushBack(entry) + } + + cacheListenerDatabaseLock.Unlock() + nextDuration := time.Duration(5) * time.Minute + if cacheListenerDatabase.Len() == 0 { + nextDuration = time.Duration(10) * time.Second + } + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameEXECUTE, `The listener cache was refreshed with the values from the database.`, fmt.Sprintf(`last count=%d`, lastCount), fmt.Sprintf(`new count=%d`, cacheListenerDatabase.Len())) + time.Sleep(nextDuration) + } + }() +} diff --git a/ICCC/Message2Data.go b/ICCC/Message2Data.go new file mode 100644 index 0000000..d5dd614 --- /dev/null +++ b/ICCC/Message2Data.go @@ -0,0 +1,95 @@ +package ICCC + +import "fmt" +import "reflect" +import "strconv" + +func message2Data(channel, command string, message interface{}) (data map[string][]string) { + if message == nil { + data = make(map[string][]string) + return + } + + element := reflect.ValueOf(message) + elementType := element.Type() + + data = make(map[string][]string) + data[`command`] = []string{command} + data[`channel`] = []string{channel} + + for i := 0; i < element.NumField(); i++ { + field := element.Field(i) + keyName := elementType.Field(i).Name + + switch field.Kind().String() { + + case `int64`: + key := fmt.Sprintf(`int:%s`, keyName) + data[key] = []string{strconv.FormatInt(field.Int(), 10)} + + case `string`: + key := fmt.Sprintf(`str:%s`, keyName) + data[key] = []string{field.String()} + + case `float64`: + key := fmt.Sprintf(`f64:%s`, keyName) + data[key] = []string{strconv.FormatFloat(field.Float(), 'f', 9, 64)} + + case `bool`: + key := fmt.Sprintf(`bool:%s`, keyName) + data[key] = []string{strconv.FormatBool(field.Bool())} + + case `uint8`: // byte + key := fmt.Sprintf(`ui8:%s`, keyName) + data[key] = []string{strconv.FormatUint(field.Uint(), 16)} + + case `slice`: + sliceLen := field.Len() + if sliceLen > 0 { + sliceKind := field.Index(0).Kind() + key := `` + dataValues := make([]string, sliceLen, sliceLen) + switch sliceKind.String() { + case `uint8`: // bytes + key = fmt.Sprintf(`ui8[]:%s`, keyName) + values := field.Interface().([]uint8) + for index, value := range values { + dataValues[index] = strconv.FormatUint(uint64(value), 16) + } + + case `int64`: + key = fmt.Sprintf(`int[]:%s`, keyName) + values := field.Interface().([]int64) + for index, value := range values { + dataValues[index] = strconv.FormatInt(value, 10) + } + + case `bool`: + key = fmt.Sprintf(`bool[]:%s`, keyName) + values := field.Interface().([]bool) + for index, value := range values { + dataValues[index] = strconv.FormatBool(value) + } + + case `string`: + key = fmt.Sprintf(`str[]:%s`, keyName) + values := field.Interface().([]string) + for index, value := range values { + dataValues[index] = value + } + + case `float64`: + key = fmt.Sprintf(`f64[]:%s`, keyName) + values := field.Interface().([]float64) + for index, value := range values { + dataValues[index] = strconv.FormatFloat(value, 'f', 9, 64) + } + } + + data[key] = dataValues + } + } + } + + return +} diff --git a/ICCC/Register2Database.go b/ICCC/Register2Database.go new file mode 100644 index 0000000..24a30db --- /dev/null +++ b/ICCC/Register2Database.go @@ -0,0 +1,47 @@ +package ICCC + +import "labix.org/v2/mgo/bson" +import "github.com/SommerEngineering/Ocean/ICCC/Scheme" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func register2Database(channel, command string) { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Register this ICCC command in to the database.`, `channel=`+channel, `command=`+command) + + // Case: Exist and active :) + emptyEntry := Scheme.Listener{} + selection := bson.D{{`Channel`, channel}, {`Command`, command}, {`IPAddressPort`, correctAddressWithPort}, {`IsActive`, true}} + count1, _ := collectionListener.Find(selection).Count() + + if count1 == 1 { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.SeverityHigh, LM.ImpactHigh, LM.MessageNameSTARTUP, `This ICCC command is already known and active.`, `Please shutdown the system next time!`) + return + } + + // Case: Exist but not active + selection = bson.D{{`Channel`, channel}, {`Command`, command}, {`IPAddressPort`, correctAddressWithPort}, {`IsActive`, false}} + notActiveEntry := Scheme.Listener{} + collectionListener.Find(selection).One(¬ActiveEntry) + + if notActiveEntry != emptyEntry { + notActiveEntry.IsActive = true + collectionListener.Update(selection, notActiveEntry) + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.SeverityNone, LM.ImpactNone, LM.MessageNameSTARTUP, `This ICCC command is already known but it was not active.`, `The command is active now!`) + return + } + + // Case: Not exist + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactNone, LM.MessageNameCONFIGURATION, `This ICCC command is not known.`, `Create now a new entry!`) + + entry := Scheme.Listener{} + entry.Channel = channel + entry.Command = command + entry.IsActive = true + entry.IPAddressPort = correctAddressWithPort + + if err := collectionListener.Insert(entry); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `It was not possible to add this ICCC command!`, err.Error(), `channel=`+channel, `command=`+command) + } else { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `This ICCC command is now known and active.`) + } +} diff --git a/ICCC/RegisterHost2Database.go b/ICCC/RegisterHost2Database.go new file mode 100644 index 0000000..e44d811 --- /dev/null +++ b/ICCC/RegisterHost2Database.go @@ -0,0 +1,26 @@ +package ICCC + +import "labix.org/v2/mgo/bson" +import "github.com/SommerEngineering/Ocean/ICCC/Scheme" +import "github.com/SommerEngineering/Ocean/Tools" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func registerHost2Database() { + host := Scheme.Host{} + host.Hostname = Tools.ThisHostname() + host.IPAddressPort = correctAddressWithPort + + selection := bson.D{{`Hostname`, host.Hostname}, {`IPAddressPort`, host.IPAddressPort}} + count, _ := collectionHosts.Find(selection).Count() + + if count == 1 { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `This host is already registered!`, `host=`+host.Hostname, `address=`+host.IPAddressPort) + } else { + if errInsert := collectionHosts.Insert(host); errInsert != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to register this host.`, errInsert.Error(), `host=`+host.Hostname, `address=`+host.IPAddressPort) + } else { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `This host is now registered.`, `host=`+host.Hostname, `address=`+host.IPAddressPort) + } + } +} diff --git a/ICCC/Registrar.go b/ICCC/Registrar.go new file mode 100644 index 0000000..ba12b09 --- /dev/null +++ b/ICCC/Registrar.go @@ -0,0 +1,15 @@ +package ICCC + +import "fmt" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func Registrar(channel, command string, callback func(data map[string][]string)) { + listenersLock.Lock() + defer listenersLock.Unlock() + + register2Database(channel, command) + listeners[fmt.Sprintf(`%s::%s`, channel, command)] = callback + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The registrar has registered a new ICCC command.`, `channel=`+channel, `command=`+command) +} diff --git a/ICCC/Scheme/Host.go b/ICCC/Scheme/Host.go new file mode 100644 index 0000000..fa18f42 --- /dev/null +++ b/ICCC/Scheme/Host.go @@ -0,0 +1,6 @@ +package Scheme + +type Host struct { + Hostname string `bson:"Hostname"` + IPAddressPort string `bson:"IPAddressPort"` +} diff --git a/ICCC/Scheme/Listener.go b/ICCC/Scheme/Listener.go new file mode 100644 index 0000000..ac4f93c --- /dev/null +++ b/ICCC/Scheme/Listener.go @@ -0,0 +1,8 @@ +package Scheme + +type Listener struct { + Channel string `bson:"Channel"` + Command string `bson:"Command"` + IsActive bool `bson:"IsActive"` + IPAddressPort string `bson:"IPAddressPort"` +} diff --git a/ICCC/Send.go b/ICCC/Send.go new file mode 100644 index 0000000..0d76686 --- /dev/null +++ b/ICCC/Send.go @@ -0,0 +1,19 @@ +package ICCC + +import "net/http" +import "net/url" +import "github.com/SommerEngineering/Ocean/Tools" +import "github.com/SommerEngineering/Ocean/ICCC/Scheme" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func sendMessage(listener Scheme.Listener, data map[string][]string) { + + valuesHTTP := url.Values(data) + valuesHTTP.Add(`InternalCommPassword`, Tools.InternalCommPassword()) + if _, err := http.PostForm(`http://`+listener.IPAddressPort+`/ICCC`, valuesHTTP); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameNETWORK, `Was not able to send the ICCC message.`, err.Error()) + } + + return +} diff --git a/ICCC/Shutdown.go b/ICCC/Shutdown.go new file mode 100644 index 0000000..56f72ef --- /dev/null +++ b/ICCC/Shutdown.go @@ -0,0 +1,30 @@ +package ICCC + +import "labix.org/v2/mgo/bson" +import "github.com/SommerEngineering/Ocean/ICCC/Scheme" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +/* +Please do not use this type. It is an internal type of Ocean to provide a shutdown function! +*/ +type ShutdownFunction struct { +} + +/* +This function is called if the Ocean server is shutting down. +*/ +func (a ShutdownFunction) Shutdown() { + Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Shutting down now all ICCC listener for this host.`) + + selection := bson.D{{`IPAddressPort`, correctAddressWithPort}} + entry := Scheme.Listener{} + iterator := collectionListener.Find(selection).Iter() + for iterator.Next(&entry) { + selectionUpdate := bson.D{{`Channel`, entry.Channel}, {`Command`, entry.Command}, {`IPAddressPort`, correctAddressWithPort}} + entry.IsActive = false + collectionListener.Update(selectionUpdate, entry) + } + + Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Done shutting down now all ICCC listener for this host.`) +} diff --git a/ICCC/Variables.go b/ICCC/Variables.go new file mode 100644 index 0000000..593642d --- /dev/null +++ b/ICCC/Variables.go @@ -0,0 +1,27 @@ +package ICCC + +import "sync" +import "container/list" +import "labix.org/v2/mgo" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +const ( + ChannelSYSTEM string = `System` + ChannelNUMGEN string = `System::NumGen` + ChannelSHUTDOWN string = `System::Shutdown` + ChannelSTARTUP string = `System::Startup` + ChannelICCC string = `System::ICCC` +) + +var ( + senderName LM.Sender = `ICCC` + db *mgo.Database = nil + collectionListener *mgo.Collection = nil + collectionHosts *mgo.Collection = nil + reservedSystemChannels []string = []string{ChannelSYSTEM, ChannelNUMGEN, ChannelSHUTDOWN, ChannelSTARTUP, ChannelICCC} + listeners map[string]func(data map[string][]string) = nil + listenersLock sync.RWMutex = sync.RWMutex{} + cacheListenerDatabase *list.List = nil + cacheListenerDatabaseLock sync.RWMutex = sync.RWMutex{} + correctAddressWithPort string = `` +) diff --git a/ICCC/WriteMessage2All.go b/ICCC/WriteMessage2All.go new file mode 100644 index 0000000..5a34b0a --- /dev/null +++ b/ICCC/WriteMessage2All.go @@ -0,0 +1,26 @@ +package ICCC + +import "github.com/SommerEngineering/Ocean/ICCC/Scheme" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func WriteMessage2All(channel, command string, message interface{}) { + cacheListenerDatabaseLock.RLock() + defer cacheListenerDatabaseLock.RUnlock() + + data := message2Data(channel, command, message) + counter := 0 + for entry := cacheListenerDatabase.Front(); entry != nil; entry = entry.Next() { + listener := entry.Value.(Scheme.Listener) + if listener.Channel == channel && listener.Command == command { + go sendMessage(listener, data) + counter++ + } + } + + if counter == 0 { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not able to deliver this message, because no listener was found!`, `channel=`+channel, `command=`+command) + } + + return +} diff --git a/ICCC/WriteMessage2Any.go b/ICCC/WriteMessage2Any.go new file mode 100644 index 0000000..3e4504a --- /dev/null +++ b/ICCC/WriteMessage2Any.go @@ -0,0 +1,31 @@ +package ICCC + +import "github.com/SommerEngineering/Ocean/Tools" +import "github.com/SommerEngineering/Ocean/ICCC/Scheme" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func WriteMessage2Any(channel, command string, message interface{}) { + cacheListenerDatabaseLock.RLock() + defer cacheListenerDatabaseLock.RUnlock() + + data := message2Data(channel, command, message) + maxCount := cacheListenerDatabase.Len() + entries := make([]Scheme.Listener, 0, maxCount) + counter := 0 + for entry := cacheListenerDatabase.Front(); entry != nil; entry = entry.Next() { + listener := entry.Value.(Scheme.Listener) + if listener.Channel == channel && listener.Command == command { + entries = entries[:len(entries)+1] + entries[counter] = listener + } + } + + count := len(entries) + if count > 0 { + listener := entries[Tools.RandomInteger(count)] + go sendMessage(listener, data) + } else { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not able to deliver this message to any listener, because no listener was found!`, `channel=`+channel, `command=`+command) + } +} diff --git a/Log/AddDevice.go b/Log/AddDevice.go new file mode 100644 index 0000000..4cd2587 --- /dev/null +++ b/Log/AddDevice.go @@ -0,0 +1,19 @@ +package Log + +import "github.com/SommerEngineering/Ocean/Log/Device" + +/* +Registering the logging devices. Normally, it is not necessary to call this function. To enable or disable a logging device, +please use the configuration database instead. But if you create your own logging device, let say a e-mail logger, then you +are able to use this function to activate your own logging device. It is save to use this function at any time and it is +thread-save ;-) +*/ +func AddLoggingDevice(device Device.Device) { + + newDevice := device + go func() { + mutexDevices.Lock() + devices.PushBack(newDevice) + mutexDevices.Unlock() + }() +} diff --git a/Log/Array.go b/Log/Array.go new file mode 100644 index 0000000..04af449 --- /dev/null +++ b/Log/Array.go @@ -0,0 +1,16 @@ +package Log + +import "container/list" +import "github.com/SommerEngineering/Ocean/Log/Meta" + +func logEntryListToArray(data *list.List) (result []Meta.Entry) { + count := data.Len() + result = make([]Meta.Entry, count, count) + position := 0 + for entry := data.Front(); entry != nil; entry = entry.Next() { + result[position] = entry.Value.(Meta.Entry) + position++ + } + + return +} diff --git a/Log/Device/Device.go b/Log/Device/Device.go new file mode 100644 index 0000000..3ce7411 --- /dev/null +++ b/Log/Device/Device.go @@ -0,0 +1,8 @@ +package Device + +import "github.com/SommerEngineering/Ocean/Log/Meta" + +type Device interface { + Log(logEntries []Meta.Entry) + Flush() +} diff --git a/Log/Device/Doc.go b/Log/Device/Doc.go new file mode 100644 index 0000000..a78305a --- /dev/null +++ b/Log/Device/Doc.go @@ -0,0 +1,4 @@ +/* +This is the logging device interface you have to fulfill to be a logging device :) +*/ +package Device diff --git a/Log/DeviceConsole/ActivateLoggingDevice.go b/Log/DeviceConsole/ActivateLoggingDevice.go new file mode 100644 index 0000000..45f7cb1 --- /dev/null +++ b/Log/DeviceConsole/ActivateLoggingDevice.go @@ -0,0 +1,7 @@ +package DeviceConsole + +import "github.com/SommerEngineering/Ocean/Log" + +func ActivateLoggingDevice() { + Log.AddLoggingDevice(Console{}) +} diff --git a/Log/DeviceConsole/Console.go b/Log/DeviceConsole/Console.go new file mode 100644 index 0000000..c174627 --- /dev/null +++ b/Log/DeviceConsole/Console.go @@ -0,0 +1,17 @@ +package DeviceConsole + +import "fmt" +import "github.com/SommerEngineering/Ocean/Log/Meta" + +type Console struct { +} + +func (dev Console) Log(entries []Meta.Entry) { + for _, entry := range entries { + fmt.Println(entry.Format()) + } +} + +func (dev Console) Flush() { + // This is not necessary for a console logger +} diff --git a/Log/DeviceConsole/Doc.go b/Log/DeviceConsole/Doc.go new file mode 100644 index 0000000..f1c216f --- /dev/null +++ b/Log/DeviceConsole/Doc.go @@ -0,0 +1,4 @@ +/* +This is the console logging device. Any received entry is logged to the console. +*/ +package DeviceConsole diff --git a/Log/DeviceDatabase/ActivateLoggingDevice.go b/Log/DeviceDatabase/ActivateLoggingDevice.go new file mode 100644 index 0000000..14d394c --- /dev/null +++ b/Log/DeviceDatabase/ActivateLoggingDevice.go @@ -0,0 +1,7 @@ +package DeviceDatabase + +import "github.com/SommerEngineering/Ocean/Log" + +func ActivateLoggingDevice() { + Log.AddLoggingDevice(Database{}) +} diff --git a/Log/DeviceDatabase/Doc.go b/Log/DeviceDatabase/Doc.go new file mode 100644 index 0000000..93b8532 --- /dev/null +++ b/Log/DeviceDatabase/Doc.go @@ -0,0 +1,5 @@ +/* +This is the database logging device. The logging events will be cached and after a time span or a maximum amount of +events, the logging entries are written to the database. +*/ +package DeviceDatabase diff --git a/Log/DeviceDatabase/Flush.go b/Log/DeviceDatabase/Flush.go new file mode 100644 index 0000000..9fdc398 --- /dev/null +++ b/Log/DeviceDatabase/Flush.go @@ -0,0 +1,14 @@ +package DeviceDatabase + +func (dev Database) Flush() { + mutexCacheFull.Lock() + defer mutexCacheFull.Unlock() + + amount := len(cache) + for counter := 0; counter < amount; counter++ { + write2Database(<-cache) + } + + logDB.Logout() + logDBSession.Close() +} diff --git a/Log/DeviceDatabase/Init.go b/Log/DeviceDatabase/Init.go new file mode 100644 index 0000000..6f20a0d --- /dev/null +++ b/Log/DeviceDatabase/Init.go @@ -0,0 +1,31 @@ +package DeviceDatabase + +import "strconv" +import "fmt" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func init() { + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting now the database logging.`) + defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting the database logging done.`) + + initDatabase() + if value, err := strconv.Atoi(ConfigurationDB.Read(`LogDBCacheSizeNumberOfEvents`)); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityHigh, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not possible to read the LogDBCacheSizeNumberOfEvents configuration.`, `The default value will be used.`, fmt.Sprintf(`Default value is %d.`, cacheSizeNumberOfEvents)) + } else { + cacheSizeNumberOfEvents = value + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Configuration LogDBCacheSizeNumberOfEvents was loaded.`, fmt.Sprintf(`The value is %d.`, cacheSizeNumberOfEvents)) + } + + if value, err := strconv.Atoi(ConfigurationDB.Read(`LogDBCacheSizeTime2FlushSeconds`)); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityHigh, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `It was not possible to read the LogDBCacheSizeTime2FlushSeconds configuration.`, `The default value will be used.`, fmt.Sprintf(`Default value is %d.`, cacheSizeTime2FlushSeconds)) + } else { + cacheSizeTime2FlushSeconds = value + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Configuration LogDBCacheSizeTime2FlushSeconds was loaded.`, fmt.Sprintf(`The value is %d.`, cacheSizeTime2FlushSeconds)) + } + + cache = make(chan LogDBEntry, cacheSizeNumberOfEvents) + initTimeout() +} diff --git a/Log/DeviceDatabase/InitDB.go b/Log/DeviceDatabase/InitDB.go new file mode 100644 index 0000000..0a51393 --- /dev/null +++ b/Log/DeviceDatabase/InitDB.go @@ -0,0 +1,165 @@ +package DeviceDatabase + +import "labix.org/v2/mgo" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func initDatabase() { + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Checking and init the logging database collection.`) + defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Checking and init the logging database collection done.`) + + databaseHost := ConfigurationDB.Read(`LogDBHost`) + databaseDB := ConfigurationDB.Read(`LogDBDatabase`) + databaseUsername := ConfigurationDB.Read(`LogDBUsername`) + databasePassword := ConfigurationDB.Read(`LogDBPassword`) + + // Connect to MongoDB: + if newSession, errDial := mgo.Dial(databaseHost); errDial != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to connect to the MongoDB host `+databaseHost, errDial.Error()) + return + } else { + logDBSession = newSession + } + + // Use the correct database: + logDB = logDBSession.DB(databaseDB) + + // Login: + if errLogin := logDB.Login(databaseUsername, databasePassword); errLogin != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to login the user `+databaseUsername, errLogin.Error()) + return + } + + // Get the collection: + logDBCollection = logDB.C(`Logbook`) + + // + // Take care about all the indexes: + // + indexTimeUTC := mgo.Index{} + indexTimeUTC.Key = []string{`TimeUTC`} + logDBCollection.EnsureIndex(indexTimeUTC) + + indexProject := mgo.Index{} + indexProject.Key = []string{`Project`} + logDBCollection.EnsureIndex(indexProject) + + indexSender := mgo.Index{} + indexSender.Key = []string{`Sender`} + logDBCollection.EnsureIndex(indexSender) + + indexCategory := mgo.Index{} + indexCategory.Key = []string{`Category`} + logDBCollection.EnsureIndex(indexCategory) + + indexLevel := mgo.Index{} + indexLevel.Key = []string{`Level`} + logDBCollection.EnsureIndex(indexLevel) + + indexSeverity := mgo.Index{} + indexSeverity.Key = []string{`Severity`} + logDBCollection.EnsureIndex(indexSeverity) + + indexImpact := mgo.Index{} + indexImpact.Key = []string{`Impact`} + logDBCollection.EnsureIndex(indexImpact) + + indexMessageName := mgo.Index{} + indexMessageName.Key = []string{`MessageName`} + logDBCollection.EnsureIndex(indexMessageName) + + indexMessageDescription := mgo.Index{} + indexMessageDescription.Key = []string{`MessageDescription`} + logDBCollection.EnsureIndex(indexMessageDescription) + + indexProjectTimeUTC := mgo.Index{} + indexProjectTimeUTC.Key = []string{`Project`, `TimeUTC`} + logDBCollection.EnsureIndex(indexProjectTimeUTC) + + indexProjectSender := mgo.Index{} + indexProjectSender.Key = []string{`Project`, `Sender`} + logDBCollection.EnsureIndex(indexProjectSender) + + indexProjectCategory := mgo.Index{} + indexProjectCategory.Key = []string{`Project`, `Category`} + logDBCollection.EnsureIndex(indexProjectCategory) + + indexProjectLevel := mgo.Index{} + indexProjectLevel.Key = []string{`Project`, `Level`} + logDBCollection.EnsureIndex(indexProjectLevel) + + indexProjectSeverity := mgo.Index{} + indexProjectSeverity.Key = []string{`Project`, `Severity`} + logDBCollection.EnsureIndex(indexProjectSeverity) + + indexProjectImpact := mgo.Index{} + indexProjectImpact.Key = []string{`Project`, `Impact`} + logDBCollection.EnsureIndex(indexProjectImpact) + + indexProjectMessageName := mgo.Index{} + indexProjectMessageName.Key = []string{`Project`, `MessageName`} + logDBCollection.EnsureIndex(indexProjectMessageName) + + indexProjectMessageDescription := mgo.Index{} + indexProjectMessageDescription.Key = []string{`Project`, `MessageDescription`} + logDBCollection.EnsureIndex(indexProjectMessageDescription) + + indexProjectTimeUTCSender := mgo.Index{} + indexProjectTimeUTCSender.Key = []string{`Project`, `TimeUTC`, `Sender`} + logDBCollection.EnsureIndex(indexProjectTimeUTCSender) + + indexProjectTimeUTCCategory := mgo.Index{} + indexProjectTimeUTCCategory.Key = []string{`Project`, `TimeUTC`, `Category`} + logDBCollection.EnsureIndex(indexProjectTimeUTCCategory) + + indexProjectTimeUTCLevel := mgo.Index{} + indexProjectTimeUTCLevel.Key = []string{`Project`, `TimeUTC`, `Level`} + logDBCollection.EnsureIndex(indexProjectTimeUTCLevel) + + indexProjectTimeUTCSeverity := mgo.Index{} + indexProjectTimeUTCSeverity.Key = []string{`Project`, `TimeUTC`, `Severity`} + logDBCollection.EnsureIndex(indexProjectTimeUTCSeverity) + + indexProjectTimeUTCImpact := mgo.Index{} + indexProjectTimeUTCImpact.Key = []string{`Project`, `TimeUTC`, `Impact`} + logDBCollection.EnsureIndex(indexProjectTimeUTCImpact) + + indexProjectTimeUTCMessageName := mgo.Index{} + indexProjectTimeUTCMessageName.Key = []string{`Project`, `TimeUTC`, `MessageName`} + logDBCollection.EnsureIndex(indexProjectTimeUTCMessageName) + + indexProjectTimeUTCMessageDescription := mgo.Index{} + indexProjectTimeUTCMessageDescription.Key = []string{`Project`, `TimeUTC`, `MessageDescription`} + logDBCollection.EnsureIndex(indexProjectTimeUTCMessageDescription) + + indexTimeUTCSender := mgo.Index{} + indexTimeUTCSender.Key = []string{`TimeUTC`, `Sender`} + logDBCollection.EnsureIndex(indexTimeUTCSender) + + indexTimeUTCCategory := mgo.Index{} + indexTimeUTCCategory.Key = []string{`TimeUTC`, `Category`} + logDBCollection.EnsureIndex(indexTimeUTCCategory) + + indexTimeUTCLevel := mgo.Index{} + indexTimeUTCLevel.Key = []string{`TimeUTC`, `Level`} + logDBCollection.EnsureIndex(indexTimeUTCLevel) + + indexTimeUTCSeverity := mgo.Index{} + indexTimeUTCSeverity.Key = []string{`TimeUTC`, `Severity`} + logDBCollection.EnsureIndex(indexTimeUTCSeverity) + + indexTimeUTCImpact := mgo.Index{} + indexTimeUTCImpact.Key = []string{`TimeUTC`, `Impact`} + logDBCollection.EnsureIndex(indexTimeUTCImpact) + + indexTimeUTCMessageName := mgo.Index{} + indexTimeUTCMessageName.Key = []string{`TimeUTC`, `MessageName`} + logDBCollection.EnsureIndex(indexTimeUTCMessageName) + + indexTimeUTCMessageDescription := mgo.Index{} + indexProjectTimeUTCMessageDescription.Key = []string{`TimeUTC`, `MessageDescription`} + logDBCollection.EnsureIndex(indexTimeUTCMessageDescription) + +} diff --git a/Log/DeviceDatabase/ReadFromCache.go b/Log/DeviceDatabase/ReadFromCache.go new file mode 100644 index 0000000..6238d21 --- /dev/null +++ b/Log/DeviceDatabase/ReadFromCache.go @@ -0,0 +1,39 @@ +package DeviceDatabase + +import "time" +import "github.com/SommerEngineering/Ocean/Shutdown" + +// Case: The cache is full +func cacheFull() { + mutexCacheFull.Lock() + defer mutexCacheFull.Unlock() + + if len(cache) < cacheSizeNumberOfEvents { + return + } + + for counter := 0; counter < cacheSizeNumberOfEvents; counter++ { + write2Database(<-cache) + } +} + +// Case: Time out +func initTimeout() { + + go func() { + for { + + if Shutdown.IsDown() { + return + } + + time.Sleep(time.Duration(cacheSizeTime2FlushSeconds) * time.Second) + mutexCacheFull.Lock() + amount := len(cache) + for counter := 0; counter < amount; counter++ { + write2Database(<-cache) + } + mutexCacheFull.Unlock() + } + }() +} diff --git a/Log/DeviceDatabase/ReceiveLogging.go b/Log/DeviceDatabase/ReceiveLogging.go new file mode 100644 index 0000000..1059cec --- /dev/null +++ b/Log/DeviceDatabase/ReceiveLogging.go @@ -0,0 +1,15 @@ +package DeviceDatabase + +import "github.com/SommerEngineering/Ocean/Log/Meta" + +type Database struct { +} + +func (dev Database) Log(entries []Meta.Entry) { + + // + // Can not log here to prevent endless loop (consumer is also producer) + // + + write2Cache(entries) +} diff --git a/Log/DeviceDatabase/Scheme.go b/Log/DeviceDatabase/Scheme.go new file mode 100644 index 0000000..5cb999a --- /dev/null +++ b/Log/DeviceDatabase/Scheme.go @@ -0,0 +1,16 @@ +package DeviceDatabase + +import "time" + +type LogDBEntry struct { + TimeUTC time.Time `bson:"TimeUTC"` + Project string `bson:"Project"` + Sender string `bson:"Sender"` + Category string `bson:"Category"` + Level string `bson:"Level"` + Severity string `bson:"Severity"` + Impact string `bson:"Impact"` + MessageName string `bson:"MessageName"` + MessageDescription string `bson:"MessageDescription"` + Parameters []string `bson:"Parameters"` +} diff --git a/Log/DeviceDatabase/Variables.go b/Log/DeviceDatabase/Variables.go new file mode 100644 index 0000000..cd93dd2 --- /dev/null +++ b/Log/DeviceDatabase/Variables.go @@ -0,0 +1,16 @@ +package DeviceDatabase + +import "sync" +import "labix.org/v2/mgo" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + senderName LM.Sender = `System::Logger::Database` + mutexCacheFull sync.Mutex = sync.Mutex{} + cache chan LogDBEntry = nil + cacheSizeNumberOfEvents int = 50 + cacheSizeTime2FlushSeconds int = 6 + logDB *mgo.Database = nil + logDBSession *mgo.Session = nil + logDBCollection *mgo.Collection = nil +) diff --git a/Log/DeviceDatabase/Write2Cache.go b/Log/DeviceDatabase/Write2Cache.go new file mode 100644 index 0000000..478b38b --- /dev/null +++ b/Log/DeviceDatabase/Write2Cache.go @@ -0,0 +1,24 @@ +package DeviceDatabase + +import "github.com/SommerEngineering/Ocean/Log/Meta" + +func write2Cache(entries []Meta.Entry) { + for _, entry := range entries { + if len(cache) == cacheSizeNumberOfEvents { + go cacheFull() + } + + logDBentry := LogDBEntry{} + logDBentry.Category = Meta.FormatCategory(entry.Category) + logDBentry.Impact = Meta.FormatImpact(entry.Impact) + logDBentry.Level = Meta.FormatLevel(entry.Level) + logDBentry.MessageDescription = entry.MessageDescription + logDBentry.MessageName = string(entry.MessageName) + logDBentry.Parameters = entry.Parameters + logDBentry.Project = entry.Project + logDBentry.Sender = string(entry.Sender) + logDBentry.Severity = Meta.FormatSeverity(entry.Severity) + logDBentry.TimeUTC = entry.Time + cache <- logDBentry + } +} diff --git a/Log/DeviceDatabase/Write2Database.go b/Log/DeviceDatabase/Write2Database.go new file mode 100644 index 0000000..12e2481 --- /dev/null +++ b/Log/DeviceDatabase/Write2Database.go @@ -0,0 +1,7 @@ +package DeviceDatabase + +func write2Database(entry LogDBEntry) { + if err := logDBCollection.Insert(entry); err != nil { + // Can not log here to prevent endless loop (consumer is also producer) + } +} diff --git a/Log/DeviceDelay.go b/Log/DeviceDelay.go new file mode 100644 index 0000000..41640f5 --- /dev/null +++ b/Log/DeviceDelay.go @@ -0,0 +1,43 @@ +package Log + +import "github.com/SommerEngineering/Ocean/Log/Meta" +import "github.com/SommerEngineering/Ocean/Log/Device" + +func deviceDelay(newEntry Meta.Entry) { + defer checkDeviceDelaySize() + + // Insert the new entry at the correct position (time)! + for logEvent := deviceDelayBuffer.Front(); logEvent != nil; logEvent = logEvent.Next() { + currentEvent := logEvent.Value.(Meta.Entry) + if newEntry.Time.Before(currentEvent.Time) { + + mutexDeviceDelays.Lock() + deviceDelayBuffer.InsertBefore(newEntry, logEvent) + mutexDeviceDelays.Unlock() + + return + } + } + + // Default: Insert at the back! + mutexDeviceDelays.Lock() + deviceDelayBuffer.PushBack(newEntry) + mutexDeviceDelays.Unlock() +} + +func checkDeviceDelaySize() { + mutexDeviceDelays.Lock() + if deviceDelayBuffer.Len() >= logDeviceDelayNumberEvents { + dataArray := logEntryListToArray(deviceDelayBuffer) + deviceDelayBuffer.Init() + + mutexDevices.RLock() + for entry := devices.Front(); entry != nil; entry = entry.Next() { + dev := entry.Value.(Device.Device) + go dev.Log(dataArray) + } + mutexDevices.RUnlock() + } + + mutexDeviceDelays.Unlock() +} diff --git a/Log/Flush.go b/Log/Flush.go new file mode 100644 index 0000000..5ac562c --- /dev/null +++ b/Log/Flush.go @@ -0,0 +1,33 @@ +package Log + +import "time" +import "github.com/SommerEngineering/Ocean/Log/Device" + +/* +Please do not call this function your self! This function allows Ocean to flush the logging at the shutting down case. +*/ +func Flush() { + mutexChannel.Lock() + channelReady = false + close(entriesBuffer) + mutexChannel.Unlock() + + // This is a bad design, but the scheduler need some time to write the last messages. + time.Sleep(15 * time.Second) + + mutexDeviceDelays.Lock() + dataArray := logEntryListToArray(deviceDelayBuffer) + deviceDelayBuffer.Init() + mutexDeviceDelays.Unlock() + + mutexDevices.RLock() + for entry := devices.Front(); entry != nil; entry = entry.Next() { + dev := entry.Value.(Device.Device) + dev.Log(dataArray) // Want to wait to complete, therefore no new thread here + go dev.Flush() + } + mutexDevices.RUnlock() + + // This is a bad design, but the devices need (may) some time to write the last messages: + time.Sleep(15 * time.Second) +} diff --git a/Log/Handlers.go b/Log/Handlers.go new file mode 100644 index 0000000..57b9229 --- /dev/null +++ b/Log/Handlers.go @@ -0,0 +1,134 @@ +package Log + +import "time" +import "fmt" +import "strings" +import "github.com/SommerEngineering/Ocean/Log/Meta" + +func writeToChannel(logEntry Meta.Entry) { + select { + case entriesBuffer <- logEntry: + case <-time.After(time.Duration(int64(logBufferTimeoutSeconds)) * time.Second): + + // Warn: Can not log here to prevent endless loop and memory leak! + fmt.Println(`Warning: Was not able to write to the logging buffer! Message=` + logEntry.Format()) + } +} + +/* +If, for any reason, you want to deliver a whole log entry to the logging system, then use this function to do so :) Normally, +just use the LogFull() and LogShort() functions instead ;) There is one reason to use this: If you have to deliver old log +events you have may imported or you have received a batch of log events of an remote system, etc. Because the LogShort() and +LogFull() methods are setting the time to the current UTC time. Therefore, TakeEntry() is the only way to provide the time! +*/ +func TakeEntry(logEntry Meta.Entry) { + + logEntry = clearEntry(logEntry) + mutexChannel.RLock() + defer mutexChannel.RUnlock() + + if !channelReady { + + mutexPreChannelBuffer.Lock() + defer mutexPreChannelBuffer.Unlock() + + preChannelBuffer.PushBack(logEntry) + return + } + + if !preChannelBufferUsed { + preChannelBufferUsed = true + mutexPreChannelBuffer.Lock() + for entry := preChannelBuffer.Front(); entry != nil; entry = entry.Next() { + writeToChannel(entry.Value.(Meta.Entry)) + } + preChannelBuffer.Init() + mutexPreChannelBuffer.Unlock() + } + + writeToChannel(logEntry) +} + +/* +Create and deliver a full log message with all fields and with the current UTC time as logging time. + + Sender The sender of this message (name of your component, like e.g. "APP::LOGIC::PAYPAL" etc.) + Category Use "CategoryBUSINESS" for business events (new payments, etc.), use "CategoryUSER" for log events + regarding users (e.g. new user registered, etc.) or "CategoryAPP" (for anything else). + Level Choose a logging level. + Severity Choose a degree of severity. + Impact Choose a degree of impact. + MessageName Choose a message name. This is like a common category for this event! + Description The logging message you want to deliver. + Parameters Provide as many additional parameters (type string) as you need. +*/ +func LogFull(sender Meta.Sender, category Meta.Category, level Meta.Level, severity Meta.Severity, impact Meta.Impact, messageName Meta.MessageName, messageDescription string, parameters ...string) { + + entry := Meta.Entry{} + entry.Project = projectName + entry.Time = time.Now().UTC() + entry.Category = category + entry.Level = level + entry.MessageDescription = messageDescription + entry.MessageName = messageName + entry.Parameters = parameters + entry.Severity = severity + entry.Impact = impact + entry.Sender = sender + + TakeEntry(entry) +} + +/* +Create and deliver a short log message with the current UTC time as logging time. The fields severity and impact +are both set to "none" value. Therefore, this short logging function does not fit for logging problems, errors etc. + + Sender The sender of this message (name of your component, like e.g. "APP::LOGIC::PAYPAL" etc.) + Category Use "CategoryBUSINESS" for business events (new payments, etc.), use "CategoryUSER" for log events + regarding users (e.g. new user registered, etc.) or "CategoryAPP" (for anything else). + Level Choose a logging level. + MessageName Choose a message name. This is like a common category for this event! + Description The logging message you want to deliver. + Parameters Provide as many additional parameters (type string) as you need. +*/ +func LogShort(sender Meta.Sender, category Meta.Category, level Meta.Level, messageName Meta.MessageName, messageDescription string, parameters ...string) { + + entry := Meta.Entry{} + entry.Project = projectName + entry.Time = time.Now().UTC() + entry.Category = category + entry.Level = level + entry.MessageDescription = messageDescription + entry.MessageName = messageName + entry.Parameters = parameters + entry.Severity = Meta.SeverityNone + entry.Impact = Meta.ImpactNone + entry.Sender = sender + + TakeEntry(entry) +} + +func clearEntry(entry Meta.Entry) (result Meta.Entry) { + entry.MessageDescription = removeWhitespaces(entry.MessageDescription) + entry.Parameters = clearParameters(entry.Parameters) + result = entry + return +} + +func clearParameters(oldParameters []string) (result []string) { + for n := 0; n < len(oldParameters); n++ { + oldParameters[n] = removeWhitespaces(oldParameters[n]) + } + + result = oldParameters + return +} + +func removeWhitespaces(text string) (result string) { + text = strings.Replace(text, "\n", ` `, -1) + text = strings.Replace(text, "\t", ` `, -1) + text = strings.Replace(text, "\r", ``, -1) + + result = text + return +} diff --git a/Log/Init.go b/Log/Init.go new file mode 100644 index 0000000..63771c8 --- /dev/null +++ b/Log/Init.go @@ -0,0 +1,52 @@ +package Log + +import "github.com/SommerEngineering/Ocean/Log/Meta" +import "strconv" +import "container/list" +import "sync" +import "path/filepath" +import "os" +import "io/ioutil" +import "strings" + +func readProjectName() { + if currentDir, dirError := os.Getwd(); dirError != nil { + panic(`Can not read the current working directory and therefore can not read the project name!`) + } else { + filename := filepath.Join(currentDir, `project.name`) + if _, errFile := os.Stat(filename); errFile != nil { + if os.IsNotExist(errFile) { + panic(`Can not read the project name file 'project.name': File not found!`) + } else { + panic(`Can not read the project name file 'project.name': ` + errFile.Error()) + } + } + + if projectNameBytes, errRead := ioutil.ReadFile(filename); errRead != nil { + panic(`Can not read the project name file 'project.name': ` + errRead.Error()) + } else { + projectName = string(projectNameBytes) + projectName = strings.TrimSpace(projectName) + } + } +} + +func init() { + readProjectName() + mutexDeviceDelays = sync.Mutex{} + mutexPreChannelBuffer = sync.Mutex{} + mutexChannel = sync.RWMutex{} + preChannelBuffer = list.New() + deviceDelayBuffer = list.New() + devices = list.New() + + initTimer() + initCode() +} + +func initCode() { + entriesBuffer = make(chan Meta.Entry, logBufferSize) + + LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, `Starting`, `The logger is now starting.`, `logBufferSize=`+strconv.Itoa(int(logBufferSize)), `logBufferTimeoutSeconds=`+strconv.Itoa(int(logBufferTimeoutSeconds))) + go scheduler(entriesBuffer) +} diff --git a/Log/LoggingIsReady.go b/Log/LoggingIsReady.go new file mode 100644 index 0000000..df3490a --- /dev/null +++ b/Log/LoggingIsReady.go @@ -0,0 +1,9 @@ +package Log + +/* +This function is used just internal by Ocean. Please do not call this function by your self! +*/ +func LoggingIsReady() { + channelReady = true + preChannelBufferUsed = false +} diff --git a/Log/Meta/Category.go b/Log/Meta/Category.go new file mode 100644 index 0000000..8f6d04b --- /dev/null +++ b/Log/Meta/Category.go @@ -0,0 +1,27 @@ +package Meta + +type Category byte + +const ( + CategoryBUSINESS = Category(iota) + CategorySYSTEM = Category(iota) + CategoryAPP = Category(iota) + CategoryUSER = Category(iota) +) + +func FormatCategory(cat Category) (result string) { + switch cat { + case CategoryBUSINESS: + result = `C:BUSINESS` + case CategoryAPP: + result = `C:APP` + case CategorySYSTEM: + result = `C:SYSTEM` + case CategoryUSER: + result = `C:USER` + default: + result = `C:N/A` + } + + return +} diff --git a/Log/Meta/Entry.go b/Log/Meta/Entry.go new file mode 100644 index 0000000..06907c9 --- /dev/null +++ b/Log/Meta/Entry.go @@ -0,0 +1,16 @@ +package Meta + +import "time" + +type Entry struct { + Project string + Time time.Time + Sender Sender + Category Category + Level Level + Severity Severity + Impact Impact + MessageName MessageName + MessageDescription string + Parameters []string +} diff --git a/Log/Meta/Format.go b/Log/Meta/Format.go new file mode 100644 index 0000000..ee228c4 --- /dev/null +++ b/Log/Meta/Format.go @@ -0,0 +1,96 @@ +package Meta + +import "fmt" +import "time" +import "strconv" +import "strings" + +func (entry Entry) Format() (result string) { + + lenSender := len(entry.Sender) + if lenSender > 40 { + lenSender = 40 + } + + lenMessageName := len(entry.MessageName) + if lenMessageName > 26 { + lenMessageName = 26 + } + + lenProject := len(entry.Project) + if lenProject > 10 { + lenProject = 10 + } + + messageDescription := entry.MessageDescription + messageDescription = strings.Replace(messageDescription, `\n`, ` `, -1) + messageDescription = strings.Replace(messageDescription, `\t`, ` `, -1) + messageDescription = strings.Replace(messageDescription, `\r`, ` `, -1) + + result = fmt.Sprintf(` [■] P:%10s [■] %s [■] %10s [■] %11s [■] %10s [■] %10s [■] sender: %-40s [■] name: %-26s [■] %s [■]`, entry.Project[:lenProject], formatTime(entry.Time), FormatCategory(entry.Category), FormatLevel(entry.Level), FormatSeverity(entry.Severity), FormatImpact(entry.Impact), entry.Sender[:lenSender], entry.MessageName[:lenMessageName], messageDescription) + + for _, param := range entry.Parameters { + + paramText := param + paramText = strings.Replace(paramText, `\n`, ` `, -1) + paramText = strings.Replace(paramText, `\t`, ` `, -1) + paramText = strings.Replace(paramText, `\r`, ` `, -1) + + result = fmt.Sprintf(`%s %s [■]`, result, paramText) + } + + return +} + +func formatTime(t1 time.Time) (result string) { + var year int = t1.Year() + var month int = int(t1.Month()) + var day int = int(t1.Day()) + var minutes int = int(t1.Minute()) + var hours int = int(t1.Hour()) + var seconds int = int(t1.Second()) + var milliseconds int = int(float64(t1.Nanosecond()) / 1000000.0) + + var monthText, dayText, minutesText, hoursText, secondsText, millisecondsText string + + if month >= 1 && month <= 9 { + monthText = fmt.Sprintf(`0%d`, month) + } else { + monthText = strconv.Itoa(month) + } + + if day >= 1 && day <= 9 { + dayText = fmt.Sprintf(`0%d`, day) + } else { + dayText = strconv.Itoa(day) + } + + if minutes >= 0 && minutes <= 9 { + minutesText = fmt.Sprintf(`0%d`, minutes) + } else { + minutesText = strconv.Itoa(minutes) + } + + if hours >= 0 && hours <= 9 { + hoursText = fmt.Sprintf(`0%d`, hours) + } else { + hoursText = strconv.Itoa(hours) + } + + if seconds >= 0 && seconds <= 9 { + secondsText = fmt.Sprintf(`0%d`, seconds) + } else { + secondsText = strconv.Itoa(seconds) + } + + if milliseconds >= 0 && milliseconds <= 9 { + millisecondsText = fmt.Sprintf(`00%d`, milliseconds) + } else if milliseconds >= 10 && milliseconds <= 99 { + millisecondsText = fmt.Sprintf(`0%d`, milliseconds) + } else { + millisecondsText = strconv.Itoa(milliseconds) + } + + result = fmt.Sprintf(`%d%s%s %s%s%s.%s`, year, monthText, dayText, hoursText, minutesText, secondsText, millisecondsText) + return +} diff --git a/Log/Meta/Impact.go b/Log/Meta/Impact.go new file mode 100644 index 0000000..9a22d45 --- /dev/null +++ b/Log/Meta/Impact.go @@ -0,0 +1,33 @@ +package Meta + +type Impact byte + +const ( + ImpactNone = Impact(iota) + ImpactLow = Impact(iota) + ImpactMiddle = Impact(iota) + ImpactHigh = Impact(iota) + ImpactCritical = Impact(iota) + ImpactUnknown = Impact(iota) +) + +func FormatImpact(pri Impact) (result string) { + switch pri { + case ImpactCritical: + result = `I:CRITICAL` + case ImpactHigh: + result = `I:HIGH` + case ImpactLow: + result = `I:LOW` + case ImpactMiddle: + result = `I:MIDDLE` + case ImpactNone: + result = `I:NONE` + case ImpactUnknown: + result = `I:UNKNOWN` + default: + result = `I:N/A` + } + + return +} diff --git a/Log/Meta/Level.go b/Log/Meta/Level.go new file mode 100644 index 0000000..281906c --- /dev/null +++ b/Log/Meta/Level.go @@ -0,0 +1,33 @@ +package Meta + +type Level byte + +const ( + LevelWARN = Level(iota) + LevelDEBUG = Level(iota) + LevelERROR = Level(iota) + LevelINFO = Level(iota) + LevelTALKATIVE = Level(iota) + LevelSECURITY = Level(iota) +) + +func FormatLevel(lvl Level) (result string) { + switch lvl { + case LevelDEBUG: + result = `L:DEBUG` + case LevelERROR: + result = `L:ERROR` + case LevelINFO: + result = `L:INFO` + case LevelSECURITY: + result = `L:SECURITY` + case LevelTALKATIVE: + result = `L:TALKATIVE` + case LevelWARN: + result = `L:WARN` + default: + result = `L:N/A` + } + + return +} diff --git a/Log/Meta/MessageNames.go b/Log/Meta/MessageNames.go new file mode 100644 index 0000000..ebf9401 --- /dev/null +++ b/Log/Meta/MessageNames.go @@ -0,0 +1,34 @@ +package Meta + +type MessageName string + +const ( + MessageNameSTARTUP = `Startup` + MessageNameINIT = `Init` + MessageNameSHUTDOWN = `Shutdown` + MessageNameEXECUTE = `Execute` + MessageNameDATABASE = `Database` + MessageNameNETWORK = `Network` + MessageNameLOGIN = `Login` + MessageNameLOGOUT = `Logout` + MessageNameSESSION = `Session` + MessageNameTIMEOUT = `Timeout` + MessageNameFILESYSTEM = `Filesystem` + MessageNameCOMMUNICATION = `Communication` + MessageNameWRITE = `Write` + MessageNameREAD = `Read` + MessageNameALGORITHM = `Algorithm` + MessageNameCONFIGURATION = `Configuration` + MessageNameTIMER = `Timer` + MessageNameINPUT = `Input` + MessageNameOUTPUT = `Output` + MessageNameBROWSER = `Browser` + MessageNameSECURITY = `Security` + MessageNameNOTFOUND = `NotFound` + MessageNameANALYSIS = `Analysis` + MessageNameSTATE = `State` + MessageNameGENERATOR = `Generator` + MessageNamePRODUCER = `Producer` + MessageNameCONSUMER = `Consumer` + MessageNamePASSWORD = `Password` +) diff --git a/Log/Meta/Sender.go b/Log/Meta/Sender.go new file mode 100644 index 0000000..c2328a9 --- /dev/null +++ b/Log/Meta/Sender.go @@ -0,0 +1,3 @@ +package Meta + +type Sender string diff --git a/Log/Meta/Severity.go b/Log/Meta/Severity.go new file mode 100644 index 0000000..922ecc6 --- /dev/null +++ b/Log/Meta/Severity.go @@ -0,0 +1,33 @@ +package Meta + +type Severity byte + +const ( + SeverityNone = Severity(iota) + SeverityLow = Severity(iota) + SeverityMiddle = Severity(iota) + SeverityHigh = Severity(iota) + SeverityCritical = Severity(iota) + SeverityUnknown = Severity(iota) +) + +func FormatSeverity(pri Severity) (result string) { + switch pri { + case SeverityCritical: + result = `S:CRITICAL` + case SeverityHigh: + result = `S:HIGH` + case SeverityLow: + result = `S:LOW` + case SeverityMiddle: + result = `S:MIDDLE` + case SeverityNone: + result = `S:NONE` + case SeverityUnknown: + result = `S:UNKNOWN` + default: + result = `S:N/A` + } + + return +} diff --git a/Log/Scheduler.go b/Log/Scheduler.go new file mode 100644 index 0000000..9390fa6 --- /dev/null +++ b/Log/Scheduler.go @@ -0,0 +1,38 @@ +package Log + +import "github.com/SommerEngineering/Ocean/Log/Meta" +import "time" + +// Note: The scheduler is the consumer for the logging channel! +func scheduler(logBuffer chan Meta.Entry) { + + LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, Meta.MessageNameSTARTUP, `The scheduler runs now.`) + var stopNextTime = false + + for { + if stopNextTime { + break + } + + nextEntry, ok := <-logBuffer + + if !ok { + // The channel was closed! + stopNextTime = true + nextEntry = Meta.Entry{} + nextEntry.Project = projectName + nextEntry.Time = time.Now().UTC() + nextEntry.Sender = senderName + nextEntry.Category = Meta.CategorySYSTEM + nextEntry.Level = Meta.LevelWARN + nextEntry.Severity = Meta.SeverityCritical + nextEntry.Impact = Meta.ImpactNone + nextEntry.MessageName = Meta.MessageNameCOMMUNICATION + nextEntry.MessageDescription = `The logging channel was closed!` + } + + deviceDelay(nextEntry) + } + + LogFull(senderName, Meta.CategorySYSTEM, Meta.LevelWARN, Meta.SeverityCritical, Meta.ImpactNone, Meta.MessageNameSHUTDOWN, `The scheduler is down now.`) +} diff --git a/Log/SetConfiguration.go b/Log/SetConfiguration.go new file mode 100644 index 0000000..732e573 --- /dev/null +++ b/Log/SetConfiguration.go @@ -0,0 +1,61 @@ +package Log + +/* +This function is used just internal by Ocean to change some configuration afterwards, after the first runtime stage +(transition from not configured state in to the desired configurated stage, after the configuration database is ready). +Please do not call this function by your self! +*/ +func SetBufferSize(bufferSize int) { + logBufferSize = bufferSize +} + +/* +This function is used just internal by Ocean to change some configuration afterwards, after the first runtime stage +(transition from not configured state in to the desired configurated stage, after the configuration database is ready). +Please do not call this function by your self! +*/ +func SetTimeoutSeconds(timeoutSeconds int) { + logBufferTimeoutSeconds = timeoutSeconds +} + +/* +This function is used just internal by Ocean to change some configuration afterwards, after the first runtime stage +(transition from not configured state in to the desired configurated stage, after the configuration database is ready). +Please do not call this function by your self! +*/ +func SetDeviceDelayNumberEvents(numberEvents int) { + logDeviceDelayNumberEvents = numberEvents +} + +/* +This function is used just internal by Ocean to change some configuration afterwards, after the first runtime stage +(transition from not configured state in to the desired configurated stage, after the configuration database is ready). +Please do not call this function by your self! +*/ +func SetDeviceDelayTimeoutSeconds(seconds int) { + logDeviceDelayTimeoutSeconds = seconds +} + +/* +This function is used just internal by Ocean to change some configuration afterwards, after the first runtime stage +(transition from not configured state in to the desired configurated stage, after the configuration database is ready). +Please do not call this function by your self! +*/ +func SetProjectName(project string) { + projectName = project +} + +/* +This function is used just internal by Ocean to change some configuration afterwards, after the first runtime stage +(transition from not configured state in to the desired configurated stage, after the configuration database is ready). +Please do not call this function by your self! +*/ +func ApplyConfigurationChanges() { + + mutexChannel.Lock() + channelReady = false + close(entriesBuffer) + mutexChannel.Unlock() + + initCode() +} diff --git a/Log/Timer.go b/Log/Timer.go new file mode 100644 index 0000000..4a59dcf --- /dev/null +++ b/Log/Timer.go @@ -0,0 +1,37 @@ +package Log + +import "time" +import "fmt" +import "github.com/SommerEngineering/Ocean/Log/Device" +import "github.com/SommerEngineering/Ocean/Log/Meta" + +func initTimer() { + + if timerIsRunning == true { + LogFull(senderName, Meta.CategorySYSTEM, Meta.LevelWARN, Meta.SeverityHigh, Meta.ImpactNone, Meta.MessageNameSTARTUP, `The logging timer is already running.`) + return + } + + timerIsRunning = true + LogShort(senderName, Meta.CategorySYSTEM, Meta.LevelINFO, Meta.MessageNameSTARTUP, `Create the logging timer now.`, fmt.Sprintf(`Timeout=%d seconds`, logDeviceDelayTimeoutSeconds)) + + go func() { + + for { + time.Sleep(time.Duration(logDeviceDelayTimeoutSeconds) * time.Second) + + mutexDeviceDelays.Lock() + dataArray := logEntryListToArray(deviceDelayBuffer) + deviceDelayBuffer.Init() + mutexDeviceDelays.Unlock() + + mutexDevices.RLock() + for entry := devices.Front(); entry != nil; entry = entry.Next() { + dev := entry.Value.(Device.Device) + go dev.Log(dataArray) + } + mutexDevices.RUnlock() + + } + }() +} diff --git a/Log/Variables.go b/Log/Variables.go new file mode 100644 index 0000000..7d4345f --- /dev/null +++ b/Log/Variables.go @@ -0,0 +1,25 @@ +package Log + +import "github.com/SommerEngineering/Ocean/Log/Meta" +import "container/list" +import "sync" + +var ( + entriesBuffer chan Meta.Entry = nil + logBufferSize int = 500 + logBufferTimeoutSeconds int = 4 + logDeviceDelayNumberEvents int = 600 + logDeviceDelayTimeoutSeconds int = 5 + channelReady bool = false + preChannelBufferUsed bool = false + preChannelBuffer *list.List = nil + deviceDelayBuffer *list.List = nil + devices *list.List = nil + mutexDeviceDelays sync.Mutex = sync.Mutex{} + mutexPreChannelBuffer sync.Mutex = sync.Mutex{} + mutexChannel sync.RWMutex = sync.RWMutex{} + mutexDevices sync.RWMutex = sync.RWMutex{} + timerIsRunning bool = false + projectName string = `not set` + senderName Meta.Sender = `System::Log` +) diff --git a/MimeTypes/Init.go b/MimeTypes/Init.go new file mode 100644 index 0000000..02edb53 --- /dev/null +++ b/MimeTypes/Init.go @@ -0,0 +1,38 @@ +package MimeTypes + +var allTypes [32]MimeType + +func init() { + allTypes[0] = TypeWebHTML + allTypes[1] = TypeWebCSS + allTypes[2] = TypeWebJavaScript + allTypes[3] = TypeXML + allTypes[4] = TypeArchiveZIP + allTypes[5] = TypeArchiveGZ + allTypes[6] = TypeWebOCTET + allTypes[7] = TypeDocumentPDF + allTypes[8] = TypeDocumentLaTeX + allTypes[9] = TypeShockwave + allTypes[10] = TypeArchiveTAR + allTypes[11] = TypeAudioWAV + allTypes[12] = TypeAudioMP3 + allTypes[13] = TypeAudioAAC + allTypes[14] = TypeAudioOGG + allTypes[15] = TypeAudioWMA + allTypes[16] = TypeImageGIF + allTypes[17] = TypeImageCommon + allTypes[18] = TypeUnknown + allTypes[19] = TypeImageJPEG + allTypes[20] = TypeImagePNG + allTypes[21] = TypePlainText + allTypes[22] = TypeVideoMPEG + allTypes[23] = TypeVideoMOV + allTypes[24] = TypeVideoAVI + allTypes[25] = TypeVideoMP4 + allTypes[26] = TypeFontEOT + allTypes[27] = TypeFontOTF + allTypes[28] = TypeImageSVG + allTypes[29] = TypeFontTTF + allTypes[30] = TypeFontWOFF + allTypes[31] = TypeWebJSON +} diff --git a/MimeTypes/KnownTypes.go b/MimeTypes/KnownTypes.go new file mode 100644 index 0000000..0f4f863 --- /dev/null +++ b/MimeTypes/KnownTypes.go @@ -0,0 +1,34 @@ +package MimeTypes + +var TypeWebHTML = MimeType{MimeType: "text/html", FileExtension: []string{".html", ".htm"}} +var TypeWebCSS = MimeType{MimeType: "text/css", FileExtension: []string{".css"}} +var TypeWebJavaScript = MimeType{MimeType: "text/javascript", FileExtension: []string{".js"}} +var TypeXML = MimeType{MimeType: "text/xml", FileExtension: []string{".xml"}} +var TypeArchiveZIP = MimeType{MimeType: "application/zip", FileExtension: []string{".zip"}} +var TypeArchiveGZ = MimeType{MimeType: "application/gzip", FileExtension: []string{".gz"}} +var TypeWebOCTET = MimeType{MimeType: "application/octet-stream", FileExtension: []string{".bin", ".exe", ".dll", ".class"}} +var TypeDocumentPDF = MimeType{MimeType: "application/pdf", FileExtension: []string{".pdf"}} +var TypeDocumentLaTeX = MimeType{MimeType: "application/x-latex", FileExtension: []string{".tex", ".latex"}} +var TypeShockwave = MimeType{MimeType: "application/x-shockwave-flash", FileExtension: []string{".swf"}} +var TypeArchiveTAR = MimeType{MimeType: "application/x-tar", FileExtension: []string{".tar"}} +var TypeAudioWAV = MimeType{MimeType: "application/x-wav", FileExtension: []string{".wav"}} +var TypeAudioMP3 = MimeType{MimeType: "audio/mpeg", FileExtension: []string{".mp3"}} +var TypeAudioAAC = MimeType{MimeType: "audio/aac", FileExtension: []string{".aac", ".m4a"}} +var TypeAudioOGG = MimeType{MimeType: "audio/ogg", FileExtension: []string{"vogg", ".oga"}} +var TypeAudioWMA = MimeType{MimeType: "audio/x-ms-wma", FileExtension: []string{".wma"}} +var TypeImageGIF = MimeType{MimeType: "image/gif", FileExtension: []string{".gif"}} +var TypeImageCommon = MimeType{MimeType: "image", FileExtension: []string{}} +var TypeUnknown = MimeType{MimeType: "application/octet-stream", FileExtension: []string{}} +var TypeImageJPEG = MimeType{MimeType: "image/jpeg", FileExtension: []string{".jpg", ".jpeg"}} +var TypeImagePNG = MimeType{MimeType: "image/png", FileExtension: []string{".png"}} +var TypePlainText = MimeType{MimeType: "text/plain", FileExtension: []string{".txt"}} +var TypeVideoMPEG = MimeType{MimeType: "video/mpeg", FileExtension: []string{".mpeg", ".mpg"}} +var TypeVideoMOV = MimeType{MimeType: "video/quicktime", FileExtension: []string{".mov", ".qt"}} +var TypeVideoAVI = MimeType{MimeType: "video/x-msvideo", FileExtension: []string{".avi"}} +var TypeVideoMP4 = MimeType{MimeType: "video/mp4", FileExtension: []string{".mp4"}} +var TypeFontEOT = MimeType{MimeType: "application/vnd.ms-fontobject", FileExtension: []string{".eot"}} +var TypeFontOTF = MimeType{MimeType: "application/font-sfnt", FileExtension: []string{".otf"}} +var TypeImageSVG = MimeType{MimeType: "image/svg+xml", FileExtension: []string{".svg"}} +var TypeFontTTF = MimeType{MimeType: "application/font-sfnt", FileExtension: []string{".ttf"}} +var TypeFontWOFF = MimeType{MimeType: "application/font-woff", FileExtension: []string{".woff"}} +var TypeWebJSON = MimeType{MimeType: "application/json", FileExtension: []string{".json"}} diff --git a/MimeTypes/MimeTypes.go b/MimeTypes/MimeTypes.go new file mode 100644 index 0000000..faf9e7e --- /dev/null +++ b/MimeTypes/MimeTypes.go @@ -0,0 +1,22 @@ +package MimeTypes + +import "strings" + +type MimeType struct { + MimeType string + FileExtension []string +} + +func DetectType(filename string) (mime MimeType, err error) { + for _, typeElement := range allTypes { + for _, extension := range typeElement.FileExtension { + if strings.HasSuffix(filename, extension) { + mime = typeElement + return + } + } + } + + mime = TypeUnknown + return +} diff --git a/MimeTypes/Write2HTTP.go b/MimeTypes/Write2HTTP.go new file mode 100644 index 0000000..db3230d --- /dev/null +++ b/MimeTypes/Write2HTTP.go @@ -0,0 +1,7 @@ +package MimeTypes + +import "net/http" + +func Write2HTTP(response http.ResponseWriter, mime MimeType) { + response.Header().Add(`Content-Type`, mime.MimeType) +} diff --git a/NumGen/BadNumber.go b/NumGen/BadNumber.go new file mode 100644 index 0000000..60ac56d --- /dev/null +++ b/NumGen/BadNumber.go @@ -0,0 +1,6 @@ +package NumGen + +func BadNumber() (result int64) { + result = badNumber64 + return +} diff --git a/NumGen/GetNumber.go b/NumGen/GetNumber.go new file mode 100644 index 0000000..eb61f4b --- /dev/null +++ b/NumGen/GetNumber.go @@ -0,0 +1,30 @@ +package NumGen + +import "net/http" +import "net/url" +import "strconv" +import "github.com/SommerEngineering/Ocean/Shutdown" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func GetNextInt64(name string) (result int64) { + result = badNumber64 + + if Shutdown.IsDown() { + return + } + + if responseData, errRequest := http.PostForm(getHandler, url.Values{"name": {name}, "password": {correctPassword}}); errRequest != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameGENERATOR, `Requesting the next number was not possible.`, errRequest.Error()) + return + } else { + nextNumberText := responseData.Header.Get(`nextNumber`) + if number, errAtio := strconv.Atoi(nextNumberText); errAtio != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameGENERATOR, `It was not possible to convert the answer into an int64.`, errAtio.Error()) + return + } else { + result = int64(number) + return + } + } +} diff --git a/NumGen/HandlerGetNext.go b/NumGen/HandlerGetNext.go new file mode 100644 index 0000000..3637248 --- /dev/null +++ b/NumGen/HandlerGetNext.go @@ -0,0 +1,41 @@ +package NumGen + +import "fmt" +import "net/http" +import "github.com/SommerEngineering/Ocean/Shutdown" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func HandlerGetNext(response http.ResponseWriter, request *http.Request) { + if Shutdown.IsDown() { + http.NotFound(response, request) + return + } + + if !isActive { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactNone, LM.MessageNameCONFIGURATION, `Called the get handler on an inactive host.`, `Wrong configuration?`) + http.NotFound(response, request) + return + } + + if correctPassword == `` { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameSECURITY, `No communication password was set.`) + http.NotFound(response, request) + return + } + + name := request.FormValue(`name`) + pwd := request.FormValue(`password`) + + if pwd != correctPassword { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityCritical, LM.ImpactNone, LM.MessageNamePASSWORD, `A wrong password was used to access this system handler.`, `This should never happens: Is this a hacking attempt?`, `IP address of requester=`+request.RemoteAddr) + http.NotFound(response, request) + return + } + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelDEBUG, LM.MessageNameANALYSIS, `Next number requested.`, name, pwd) + channel := requestChannel4Name(name) + nextNumber := <-channel + + response.Header().Add(`nextNumber`, fmt.Sprintf(`%d`, nextNumber)) +} diff --git a/NumGen/Init.go b/NumGen/Init.go new file mode 100644 index 0000000..1b27624 --- /dev/null +++ b/NumGen/Init.go @@ -0,0 +1,38 @@ +package NumGen + +import "strings" +import "strconv" +import "github.com/SommerEngineering/Ocean/Tools" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func init() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Init the number generator.`) + + channelListLock.Lock() + defer channelListLock.Unlock() + + correctPassword = ConfigurationDB.Read(`InternalCommPassword`) + activeHost := ConfigurationDB.Read(`NumGenActiveHosts`) + isActive = strings.Contains(activeHost, Tools.ThisHostname()) + getHandler = ConfigurationDB.Read(`NumGenGetHandler`) + + if isActive { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.MessageNameCONFIGURATION, `The number generator is active on this host.`, `This host is producer and consumer.`) + + channelBufferSizeText := ConfigurationDB.Read(`NumGenBufferSize`) + if bufferSizeNumber, errBufferSizeNumber := strconv.Atoi(channelBufferSizeText); errBufferSizeNumber != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactMiddle, LM.MessageNameCONFIGURATION, `Was not able to parse the configuration value of NumGenBufferSize.`, errBufferSizeNumber.Error(), `Use the default value now!`) + } else { + channelBufferSize = bufferSizeNumber + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The buffer size for the number generator was loaded.`, `Buffer size=`+channelBufferSizeText) + } + + channelList = make(map[string]chan int64) + + initDB() + } else { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.MessageNameCONFIGURATION, `The number generator is not active on this host.`, `This host is just a consumer.`) + } +} diff --git a/NumGen/InitDB.go b/NumGen/InitDB.go new file mode 100644 index 0000000..1122a43 --- /dev/null +++ b/NumGen/InitDB.go @@ -0,0 +1,28 @@ +package NumGen + +import "labix.org/v2/mgo" +import "github.com/SommerEngineering/Ocean/CustomerDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func initDB() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Start init of number generator collection.`) + defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Done init of number generator collection.`) + + // Get the database: + db = CustomerDB.DB() + + if db == nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to get the customer database.`) + return + } + + // Get my collection: + collectionNumGen = db.C(`NumGen`) + + // Take care about the indexes: + indexName := mgo.Index{} + indexName.Key = []string{`Name`} + indexName.Unique = true + collectionNumGen.EnsureIndex(indexName) +} diff --git a/NumGen/Producer.go b/NumGen/Producer.go new file mode 100644 index 0000000..6e7101a --- /dev/null +++ b/NumGen/Producer.go @@ -0,0 +1,68 @@ +package NumGen + +import "time" +import "labix.org/v2/mgo/bson" +import "github.com/SommerEngineering/Ocean/Shutdown" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func producer(name string) { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The NumGen producer is now starting.`, `name=`+name) + + // Get my channel: + myChannel := requestChannel4Name(name) + + // Read my next free number: + currentNextFreeNumber := nextFreeNumberFromDatabase(name) + + // Where is the next "reload"? + nextReload := currentNextFreeNumber + int64(channelBufferSize) + + // Set the next free number to the database: + updateNextFreeNumber2Database(name, nextReload) + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The NumGen producer is now running.`, `name=`+name) + for nextNumber := currentNextFreeNumber; true; { + if Shutdown.IsDown() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSHUTDOWN, `The NumGen producer is now down.`, `name=`+name) + return + } + + if nextNumber > nextReload { + nextReload = nextReload + int64(channelBufferSize) + updateNextFreeNumber2Database(name, nextReload) + + // Enables the administrator to monitor the frequence of chunks and is able to reconfigure the settings: + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelDEBUG, LM.MessageNamePRODUCER, `The NumGen producer creates the next chunk.`, `name=`+name) + } + + // Enqueue the next number: + select { + case myChannel <- nextNumber: + nextNumber++ + case <-time.After(time.Millisecond * 500): + } + } +} + +func nextFreeNumberFromDatabase(name string) (result int64) { + selection := bson.D{{`Name`, name}} + searchResult := NumberGenScheme{} + + count, _ := collectionNumGen.Find(selection).Count() + if count == 1 { + collectionNumGen.Find(selection).One(&searchResult) + result = searchResult.NextFreeNumber + } else { + searchResult.Name = name + searchResult.NextFreeNumber = startValue64 + collectionNumGen.Insert(searchResult) + result = searchResult.NextFreeNumber + } + return +} + +func updateNextFreeNumber2Database(name string, nextFreeNumber int64) { + selection := bson.D{{`Name`, name}} + collectionNumGen.Update(selection, NumberGenScheme{Name: name, NextFreeNumber: nextFreeNumber}) +} diff --git a/NumGen/RequestChannel.go b/NumGen/RequestChannel.go new file mode 100644 index 0000000..a43cf6b --- /dev/null +++ b/NumGen/RequestChannel.go @@ -0,0 +1,38 @@ +package NumGen + +import "github.com/SommerEngineering/Ocean/Shutdown" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func requestChannel4Name(name string) (result chan int64) { + + if Shutdown.IsDown() { + return + } + + if !isActive { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactNone, LM.MessageNameCONFIGURATION, `Called the requestChannel4Name() on an inactive host.`, `Wrong configuration?`) + return + } + + channelListLock.RLock() + channel, isPresent := channelList[name] + channelListLock.RUnlock() + + if isPresent { + result = channel + return + } + + // Create the entry: + newChannel := make(chan int64, channelBufferSize) + result = newChannel + + channelListLock.Lock() + channelList[name] = newChannel + channelListLock.Unlock() + + // Create the new producer: + go producer(name) + return +} diff --git a/NumGen/Scheme.go b/NumGen/Scheme.go new file mode 100644 index 0000000..7ee7c58 --- /dev/null +++ b/NumGen/Scheme.go @@ -0,0 +1,6 @@ +package NumGen + +type NumberGenScheme struct { + Name string `bson:"Name"` + NextFreeNumber int64 `bson:"NextFreeNumber"` +} diff --git a/NumGen/Shutdown.go b/NumGen/Shutdown.go new file mode 100644 index 0000000..a2d2fed --- /dev/null +++ b/NumGen/Shutdown.go @@ -0,0 +1,11 @@ +package NumGen + +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +type ShutdownFunction struct { +} + +func (a ShutdownFunction) Shutdown() { + Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Shutting down the number generator.`) +} diff --git a/NumGen/Variables.go b/NumGen/Variables.go new file mode 100644 index 0000000..3bf18c5 --- /dev/null +++ b/NumGen/Variables.go @@ -0,0 +1,22 @@ +package NumGen + +import "sync" +import "labix.org/v2/mgo" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + correctPassword string = `` + senderName LM.Sender = `System::NumGen::Producer` + isActive bool = false + getHandler string = `` + db *mgo.Database = nil + collectionNumGen *mgo.Collection = nil + channelBufferSize int = 10 + channelList map[string]chan int64 = nil + channelListLock sync.RWMutex = sync.RWMutex{} +) + +const ( + badNumber64 int64 = 9222222222222222222 + startValue64 int64 = -9223372036854775808 +) diff --git a/Robots/Handler.go b/Robots/Handler.go new file mode 100644 index 0000000..9a59de6 --- /dev/null +++ b/Robots/Handler.go @@ -0,0 +1,17 @@ +package Robots + +import "fmt" +import "net/http" +import "github.com/SommerEngineering/Ocean/Shutdown" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func HandlerRobots(response http.ResponseWriter, request *http.Request) { + if Shutdown.IsDown() { + http.NotFound(response, request) + return + } + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameNETWORK, `The robots.txt was requested.`, request.RemoteAddr) + fmt.Fprintf(response, `%s`, robotsContent) +} diff --git a/Robots/Init.go b/Robots/Init.go new file mode 100644 index 0000000..43b37b6 --- /dev/null +++ b/Robots/Init.go @@ -0,0 +1,10 @@ +package Robots + +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func init() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Init the robots component.`) + robotsContent = ConfigurationDB.Read(`robots.txt`) +} diff --git a/Robots/Variables.go b/Robots/Variables.go new file mode 100644 index 0000000..754af18 --- /dev/null +++ b/Robots/Variables.go @@ -0,0 +1,9 @@ +package Robots + +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + senderName LM.Sender = `System::Robots` + robotsContent string = `User-agent: * +Disallow: /` +) diff --git a/Shutdown/Check.go b/Shutdown/Check.go new file mode 100644 index 0000000..41641db --- /dev/null +++ b/Shutdown/Check.go @@ -0,0 +1,6 @@ +package Shutdown + +func IsDown() (result bool) { + result = stopAllRequests + return +} diff --git a/Shutdown/Init.go b/Shutdown/Init.go new file mode 100644 index 0000000..ca3a82b --- /dev/null +++ b/Shutdown/Init.go @@ -0,0 +1,13 @@ +package Shutdown + +import "container/list" +import "os/signal" +import "os" + +func InitShutdown() { + shutdownHandlers = list.New() + + // Apply the shutdown handler: + signal.Notify(shutdownSignal, os.Interrupt, os.Kill) + go executeShutdown() +} diff --git a/Shutdown/Shutdown.go b/Shutdown/Shutdown.go new file mode 100644 index 0000000..c61852c --- /dev/null +++ b/Shutdown/Shutdown.go @@ -0,0 +1,29 @@ +package Shutdown + +import "os" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +type ShutdownHandler interface { + Shutdown() +} + +func AddShutdownHandler(handler ShutdownHandler) { + shutdownHandlers.PushBack(handler) +} + +func executeShutdown() { + sig := <-shutdownSignal + stopAllRequests = true + + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameSHUTDOWN, `The system was called to shutting down.`, sig.String(), `Call now all shutdown handlers.`) + for handler := shutdownHandlers.Front(); handler != nil; handler = handler.Next() { + h := handler.Value.(ShutdownHandler) + h.Shutdown() + } + + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameSHUTDOWN, `The system is shutting down now.`) + Log.Flush() + + os.Exit(6) +} diff --git a/Shutdown/Variables.go b/Shutdown/Variables.go new file mode 100644 index 0000000..b283723 --- /dev/null +++ b/Shutdown/Variables.go @@ -0,0 +1,12 @@ +package Shutdown + +import "os" +import "container/list" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + shutdownSignal chan os.Signal = make(chan os.Signal) + shutdownHandlers *list.List = nil + senderName LM.Sender = `System::Shutdown` + stopAllRequests bool = false +) diff --git a/StaticFiles/FindAndReadFile.go b/StaticFiles/FindAndReadFile.go new file mode 100644 index 0000000..69152c5 --- /dev/null +++ b/StaticFiles/FindAndReadFile.go @@ -0,0 +1,47 @@ +package StaticFiles + +import "io/ioutil" +import "bytes" +import "archive/zip" +import "github.com/SommerEngineering/Ocean/Shutdown" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func FindAndReadFile(filename string) (result []byte) { + if Shutdown.IsDown() { + return + } + + // Prepare the path: + path := filename + + // Read the content from the ZIP file: + reader, readerError := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData))) + if readerError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameREAD, `Was not able to read the ZIP file.`, readerError.Error()) + return + } + + for _, file := range reader.File { + if file.Name == path { + + fileReader, openError := file.Open() + defer fileReader.Close() + + if openError == nil { + contentData, readError := ioutil.ReadAll(fileReader) + + if readError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameREAD, `Was not able to read the content of the desired file.`, readError.Error(), path) + return + } + + result = contentData + return + } + } + } + + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNOTFOUND, `The desired file is not part of the ZIP file.`, `Do you use an old version?`, path) + return +} diff --git a/StaticFiles/Handler.go b/StaticFiles/Handler.go new file mode 100644 index 0000000..8c1c6e9 --- /dev/null +++ b/StaticFiles/Handler.go @@ -0,0 +1,57 @@ +package StaticFiles + +import "fmt" +import "strings" +import "net/http" +import "github.com/SommerEngineering/Ocean/Shutdown" +import "github.com/SommerEngineering/Ocean/MimeTypes" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func HandlerStaticFiles(response http.ResponseWriter, request *http.Request) { + if Shutdown.IsDown() { + http.NotFound(response, request) + return + } + + // Prepare the path: + path := strings.Replace(request.RequestURI, `/staticFiles/`, ``, 1) + path = strings.Replace(path, `%20`, ` `, -1) + fileType := `` + + // Determine the MIME type: + if mimeType, errMime := MimeTypes.DetectType(path); errMime != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityMiddle, LM.ImpactMiddle, LM.MessageNameNOTFOUND, `Was not able to detect the MIME type of the font.`, errMime.Error(), path) + http.NotFound(response, request) + return + } else { + fileType = mimeType.MimeType + } + + if fileType == `` { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameNOTFOUND, `The mime type is unknown.`, path) + http.NotFound(response, request) + return + } + + contentData := FindAndReadFile(path) + if contentData == nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `The desired file was not found.`, path) + http.NotFound(response, request) + return + } + + fileLenText := fmt.Sprintf(`%d`, len(contentData)) + response.Header().Add(`Content-Length`, fileLenText) + response.Header().Add(`Content-Type`, fileType) + response.Write(contentData) + + if logStaticFileRequests { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameBROWSER, `A static file was requested.`, path) + } + return + + http.NotFound(response, request) + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNOTFOUND, `The desired file is not part of the ZIP file.`, `Do you use an old version?`, path) + return +} diff --git a/StaticFiles/Init.go b/StaticFiles/Init.go new file mode 100644 index 0000000..fa3138b --- /dev/null +++ b/StaticFiles/Init.go @@ -0,0 +1,44 @@ +package StaticFiles + +import "io/ioutil" +import "github.com/SommerEngineering/Ocean/CustomerDB" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func init() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting now the static files component.`) + defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting the static files component done.`) + + if ConfigurationDB.Read(`EnableStaticFiles`) != `true` { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Static files are disabled.`) + return + } + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Static files are enabled.`) + + // Read the configuration: + if ConfigurationDB.Read(`MapStaticFiles2Root`) == `true` { + startFile4Map2Root = ConfigurationDB.Read(`MapStaticFiles2RootRootFile`) + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The desired root document was set.`, `rootDocument=`+startFile4Map2Root) + } + + logStaticFileRequests = ConfigurationDB.Read(`LogStaticFileRequests`) == `true` + + // Read the static files' data from GridFS: + gridFS := CustomerDB.GridFS() + if gridFile, errGridFile := gridFS.Open(`staticFiles.zip`); errGridFile != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to open the static files out of the GridFS!`, errGridFile.Error()) + return + } else { + defer gridFile.Close() + if data, ioError := ioutil.ReadAll(gridFile); ioError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to read the static files.`, ioError.Error()) + return + } else { + zipData = data + } + } + + return +} diff --git a/StaticFiles/Map2Root.go b/StaticFiles/Map2Root.go new file mode 100644 index 0000000..1131e1d --- /dev/null +++ b/StaticFiles/Map2Root.go @@ -0,0 +1,19 @@ +package StaticFiles + +import "net/http" +import "github.com/SommerEngineering/Ocean/Shutdown" + +func HandlerMapStaticFiles2Root(response http.ResponseWriter, request *http.Request) { + if Shutdown.IsDown() { + http.NotFound(response, request) + return + } + + if request.RequestURI == `/` { + request.RequestURI = `/staticFiles/` + startFile4Map2Root + } else { + request.RequestURI = `/staticFiles` + request.RequestURI + } + + HandlerStaticFiles(response, request) +} diff --git a/StaticFiles/Variables.go b/StaticFiles/Variables.go new file mode 100644 index 0000000..314bfdb --- /dev/null +++ b/StaticFiles/Variables.go @@ -0,0 +1,10 @@ +package StaticFiles + +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + senderName LM.Sender = `System::StaticFiles` + startFile4Map2Root string = `index.html` + logStaticFileRequests bool = false + zipData []byte = nil +) diff --git a/System/ICCCStart.go b/System/ICCCStart.go new file mode 100644 index 0000000..503cd1c --- /dev/null +++ b/System/ICCCStart.go @@ -0,0 +1,8 @@ +package System + +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func icccSystemStart(data map[string][]string) { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The system is now up and ready.`) +} diff --git a/System/Init.go b/System/Init.go new file mode 100644 index 0000000..3cee79e --- /dev/null +++ b/System/Init.go @@ -0,0 +1,90 @@ +package System + +import "fmt" +import "runtime" +import "strconv" +import "github.com/SommerEngineering/Ocean/ICCC" +import "github.com/SommerEngineering/Ocean/Shutdown" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/CustomerDB" +import "github.com/SommerEngineering/Ocean/NumGen" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func initSystem() { + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The system is now starting.`) + + // Set the desired amount of CPUs: + utilizeCPUs := 2 + if value, err := strconv.Atoi(ConfigurationDB.Read(`OceanUtilizeCPUs`)); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to read the OceanUtilizeCPUs configuration.`, `Use the default value instead.`) + } else { + utilizeCPUs = value + } + + runtime.GOMAXPROCS(utilizeCPUs) + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Configuration OceanUtilizeCPUs is set.`, fmt.Sprintf(`value=%d`, utilizeCPUs)) + + // Apply all desired logging devices: + initLoggingDevices() + + // Set the logging buffer size: + logBufferSize := 500 + if value, err := strconv.Atoi(ConfigurationDB.Read(`LogBufferSize`)); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to read the LogBufferSize configuration.`, `Use the default value instead.`) + } else { + logBufferSize = value + } + + Log.SetBufferSize(logBufferSize) + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Configuration LogBufferSize is set.`, fmt.Sprintf(`value=%d`, logBufferSize)) + + // Set the logging device delay (number of events): + logDeviceDelayNumberEvents := 600 + if value, err := strconv.Atoi(ConfigurationDB.Read(`LogDeviceDelayNumberEvents`)); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to read the LogDeviceDelayNumberEvents configuration.`, `Use the default value instead.`) + } else { + logDeviceDelayNumberEvents = value + } + + Log.SetDeviceDelayNumberEvents(logDeviceDelayNumberEvents) + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Configuration LogDeviceDelayNumberEvents is set.`, fmt.Sprintf(`value=%d`, logDeviceDelayNumberEvents)) + + // Set the logging device delay time to flush (seconds): + logDeviceDelayTime2FlushSeconds := 5 + if value, err := strconv.Atoi(ConfigurationDB.Read(`LogDeviceDelayTime2FlushSeconds`)); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to read the LogDeviceDelayTime2FlushSeconds configuration.`, `Use the default value instead.`) + } else { + logDeviceDelayTime2FlushSeconds = value + } + + Log.SetDeviceDelayTimeoutSeconds(logDeviceDelayTime2FlushSeconds) + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Configuration LogDeviceDelayTime2FlushSeconds is set.`, fmt.Sprintf(`value=%d`, logDeviceDelayTime2FlushSeconds)) + + // Set the logging timeout (seconds): + logTimeoutSeconds := 3 + if value, err := strconv.Atoi(ConfigurationDB.Read(`LogTimeoutSeconds`)); err != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to read the LogTimeoutSeconds configuration.`, `Use the default value instead.`) + } else { + logTimeoutSeconds = value + } + + Log.SetTimeoutSeconds(logTimeoutSeconds) + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Configuration LogTimeoutSeconds is set.`, fmt.Sprintf(`value=%d`, logTimeoutSeconds)) + + // Apply these changes: + Log.ApplyConfigurationChanges() + Log.LoggingIsReady() + + // Register all system shutdown handlers: + Shutdown.InitShutdown() + Shutdown.AddShutdownHandler(ICCC.ShutdownFunction{}) + Shutdown.AddShutdownHandler(NumGen.ShutdownFunction{}) + Shutdown.AddShutdownHandler(ConfigurationDB.ShutdownFunction{}) + Shutdown.AddShutdownHandler(CustomerDB.ShutdownFunction{}) + // The logging subsystem is not registered here, because it will be automated called at the end + + // Register all system ICCC commands: + ICCC.Registrar(ICCC.ChannelSYSTEM, `System::Start`, icccSystemStart) +} diff --git a/System/InitHandlers.go b/System/InitHandlers.go new file mode 100644 index 0000000..3be9462 --- /dev/null +++ b/System/InitHandlers.go @@ -0,0 +1,30 @@ +package System + +import "net/http" +import "github.com/SommerEngineering/Ocean/ICCC" +import "github.com/SommerEngineering/Ocean/WebContent" +import "github.com/SommerEngineering/Ocean/StaticFiles" +import "github.com/SommerEngineering/Ocean/NumGen" +import "github.com/SommerEngineering/Ocean/Robots" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func InitHandlers() { + + initSystem() + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Register now all system handlers.`) + + http.HandleFunc(`/framework/`, WebContent.HandlerDeliverFramework) + http.HandleFunc(`/staticFiles/`, StaticFiles.HandlerStaticFiles) + http.HandleFunc(`/next/number`, NumGen.HandlerGetNext) + http.HandleFunc(`/robots.txt`, Robots.HandlerRobots) + http.HandleFunc(`/ICCC`, ICCC.ICCCHandler) + + if ConfigurationDB.Read(`MapStaticFiles2Root`) == "true" { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The static files are mapped to the root.`) + http.HandleFunc(`/`, StaticFiles.HandlerMapStaticFiles2Root) + } + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Done with registering all system handler.`) +} diff --git a/System/RegisterLoggingDevices.go b/System/RegisterLoggingDevices.go new file mode 100644 index 0000000..2ca2051 --- /dev/null +++ b/System/RegisterLoggingDevices.go @@ -0,0 +1,35 @@ +package System + +import "github.com/SommerEngineering/Ocean/Log/DeviceConsole" +import "github.com/SommerEngineering/Ocean/Log/DeviceDatabase" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func initLoggingDevices() { + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Init the logging devices.`) + defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Init the logging devices done.`) + + if ConfigurationDB.Read(`LogUseDatabaseLogging`) == `true` { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The database logger is active.`) + activateDatabaseLogger() + } else { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The database logger is NOT active.`) + } + + if ConfigurationDB.Read(`LogUseConsoleLogging`) == `true` { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The console logger is active.`) + activateConsoleLogger() + } else { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `The console logger is NOT active.`) + } +} + +func activateDatabaseLogger() { + DeviceDatabase.ActivateLoggingDevice() +} + +func activateConsoleLogger() { + DeviceConsole.ActivateLoggingDevice() +} diff --git a/System/Start.go b/System/Start.go new file mode 100644 index 0000000..11aa38e --- /dev/null +++ b/System/Start.go @@ -0,0 +1,18 @@ +package System + +import "net/http" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/ICCC" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func StartAndBlockForever() { + oceanHostnameAndPort := ConfigurationDB.Read(`OceanHostnameAndPort`) + + // Init ICCC: + ICCC.WriteMessage2All(ICCC.ChannelSYSTEM, `System::Start`, nil) + + // Start and block: + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Web server is now listening.`, `Configuration for hostname and port.`, oceanHostnameAndPort) + http.ListenAndServe(oceanHostnameAndPort, nil) +} diff --git a/System/Variables.go b/System/Variables.go new file mode 100644 index 0000000..205d7b2 --- /dev/null +++ b/System/Variables.go @@ -0,0 +1,7 @@ +package System + +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + senderName LM.Sender = `System::System` +) diff --git a/Templates/Init.go b/Templates/Init.go new file mode 100644 index 0000000..e811415 --- /dev/null +++ b/Templates/Init.go @@ -0,0 +1,59 @@ +package Templates + +import "fmt" +import "bytes" +import "html/template" +import "io/ioutil" +import "archive/zip" +import "github.com/SommerEngineering/Ocean/CustomerDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func init() { + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting the template engine.`) + defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Starting the template engine done.`) + + gridFS := CustomerDB.GridFS() + if gridFile, errGridFile := gridFS.Open(`templates.zip`); errGridFile != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to open the templates out of the GridFS!`, errGridFile.Error()) + return + } else { + defer gridFile.Close() + if data, ioError := ioutil.ReadAll(gridFile); ioError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to read the templates.`, ioError.Error()) + return + } else { + zipData = data + } + } + + // Read the content from the ZIP file: + reader, readerError := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData))) + if readerError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameREAD, `Was not able to read the ZIP file.`, readerError.Error()) + return + } + + templates = template.New(`root`) + for _, file := range reader.File { + + fileReader, openError := file.Open() + if openError == nil { + contentData, readError := ioutil.ReadAll(fileReader) + fileReader.Close() + + if readError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameREAD, `Was not able to read the content of the desired template.`, readError.Error(), file.FileInfo().Name()) + continue + } + + templateData := string(contentData) + templates.Parse(templateData) + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameEXECUTE, fmt.Sprintf(`The template '%s' was parsed.`, file.FileInfo().Name())) + } else { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to open a template.`, file.FileInfo().Name()) + } + } + + isInit = true +} diff --git a/Templates/Process.go b/Templates/Process.go new file mode 100644 index 0000000..e684f4d --- /dev/null +++ b/Templates/Process.go @@ -0,0 +1,19 @@ +package Templates + +import "net/http" +import "github.com/SommerEngineering/Ocean/MimeTypes" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func ProcessHTML(templateName string, response http.ResponseWriter, data interface{}) { + + if !isInit { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameINIT, `The template engine is not (yet) init.`) + return + } + + MimeTypes.Write2HTTP(response, MimeTypes.TypeWebHTML) + if executeError := templates.ExecuteTemplate(response, templateName, data); executeError != nil { + Log.LogFull(senderName, LM.CategoryAPP, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameEXECUTE, `Was not able to execute the template.`, templateName, executeError.Error()) + } +} diff --git a/Templates/Variables.go b/Templates/Variables.go new file mode 100644 index 0000000..22769a4 --- /dev/null +++ b/Templates/Variables.go @@ -0,0 +1,11 @@ +package Templates + +import "html/template" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + templates *template.Template = nil + senderName LM.Sender = `System::Templates` + zipData []byte = nil + isInit bool = false +) diff --git a/Tools/Hostname.go b/Tools/Hostname.go new file mode 100644 index 0000000..b3a1db2 --- /dev/null +++ b/Tools/Hostname.go @@ -0,0 +1,6 @@ +package Tools + +func ThisHostname() (result string) { + result = hostname + return +} diff --git a/Tools/IPAddresses.go b/Tools/IPAddresses.go new file mode 100644 index 0000000..e8b542a --- /dev/null +++ b/Tools/IPAddresses.go @@ -0,0 +1,42 @@ +package Tools + +import "net" +import "strings" + +func ReadAllIPAddresses4ThisHost() (addresses4Host []string) { + addresses4Host = ipAddresses + return +} + +func initIPAddresses4ThisHost() { + addresses, err := net.InterfaceAddrs() + if err != nil { + ipAddresses = make([]string, 1) + ipAddresses[0] = `127.0.0.1` + return + } + + counter := 0 + ipAddresses = make([]string, len(addresses)) + for _, address := range addresses { + addressText := address.String() + if strings.Contains(addressText, `/`) { + addressText = addressText[:strings.Index(addressText, `/`)] + } + + ip := net.ParseIP(addressText) + if !ip.IsLoopback() && !ip.IsUnspecified() { + ipAddresses[counter] = ip.String() + counter++ + } + } + + if counter == 0 { + ipAddresses = make([]string, 1) + ipAddresses[0] = `127.0.0.1` + } else { + ipAddresses = ipAddresses[:counter] + } + + return +} diff --git a/Tools/Init.go b/Tools/Init.go new file mode 100644 index 0000000..bfc73ea --- /dev/null +++ b/Tools/Init.go @@ -0,0 +1,27 @@ +package Tools + +import "os" +import "time" +import "math/rand" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func init() { + // Get this hostname: + if hostText, errHost := os.Hostname(); errHost != nil { + panic(`Was not able to read the hostname: ` + errHost.Error()) + } else { + hostname = hostText + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Log the hostname of this machine.`, hostname) + } + + // Get all IP addresses: + initIPAddresses4ThisHost() + + // Read the InternalCommPassword: + internalCommPassword = ConfigurationDB.Read(`InternalCommPassword`) + + // Set the seed for random: + rand.Seed(time.Now().Unix()) +} diff --git a/Tools/InternalCommPassword.go b/Tools/InternalCommPassword.go new file mode 100644 index 0000000..4322361 --- /dev/null +++ b/Tools/InternalCommPassword.go @@ -0,0 +1,6 @@ +package Tools + +func InternalCommPassword() (pwd string) { + pwd = internalCommPassword + return +} diff --git a/Tools/Random.go b/Tools/Random.go new file mode 100644 index 0000000..f19239e --- /dev/null +++ b/Tools/Random.go @@ -0,0 +1,8 @@ +package Tools + +import "math/rand" + +func RandomInteger(max int) (rnd int) { + rnd = rand.Intn(max) + return +} diff --git a/Tools/Variables.go b/Tools/Variables.go new file mode 100644 index 0000000..d1711ac --- /dev/null +++ b/Tools/Variables.go @@ -0,0 +1,10 @@ +package Tools + +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + senderName LM.Sender = `System::Tools` + hostname string = `unknown` + ipAddresses []string = nil + internalCommPassword string = `` +) diff --git a/WebContent/GetContent.go b/WebContent/GetContent.go new file mode 100644 index 0000000..c43f911 --- /dev/null +++ b/WebContent/GetContent.go @@ -0,0 +1,43 @@ +package WebContent + +import "errors" +import "archive/zip" +import "io/ioutil" +import "bytes" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func GetContent(path string) (content []byte, err error) { + + reader, readerError := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData))) + if readerError != nil { + err = errors.New("Was not able to read the ZIP file: " + readerError.Error()) + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameREAD, `Was not able to read the ZIP file.`, readerError.Error()) + return + } + + for _, file := range reader.File { + if file.Name == path { + + fileReader, openError := file.Open() + defer fileReader.Close() + + if openError == nil { + contentData, readError := ioutil.ReadAll(fileReader) + + if readError != nil { + err = errors.New("Was not able to read the content of the desired file: " + readError.Error()) + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameREAD, `Was not able to read the content of the desired file.`, readError.Error(), path) + return + } + + content = contentData + return + } + } + } + + err = errors.New("File not found!") + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNOTFOUND, `The desired file is not part of the ZIP file.`, `Do you use an old version?`, path) + return +} diff --git a/WebContent/Handler.go b/WebContent/Handler.go new file mode 100644 index 0000000..131c383 --- /dev/null +++ b/WebContent/Handler.go @@ -0,0 +1,22 @@ +package WebContent + +import "net/http" +import "strings" +import "github.com/SommerEngineering/Ocean/Shutdown" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func HandlerDeliverFramework(response http.ResponseWriter, request *http.Request) { + if Shutdown.IsDown() { + http.NotFound(response, request) + return + } + + path := strings.Replace(request.URL.Path, "/framework/", "", 1) + sendError := SendContent(response, path) + + if sendError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameWRITE, `Was not able to send the desired web content file.`, request.URL.Path, sendError.Error()) + http.NotFound(response, request) + } +} diff --git a/WebContent/Init.go b/WebContent/Init.go new file mode 100644 index 0000000..0c91c99 --- /dev/null +++ b/WebContent/Init.go @@ -0,0 +1,36 @@ +package WebContent + +import "io/ioutil" +import "github.com/SommerEngineering/Ocean/CustomerDB" +import "github.com/SommerEngineering/Ocean/ConfigurationDB" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func init() { + + Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Run init for the web content.`) + defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Init for web content done.`) + + if isInit { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityHigh, LM.ImpactNone, LM.MessageNameINIT, `The web content is already fine.`) + return + } + + filename = ConfigurationDB.Read(`FilenameWebResources`) + gridFS := CustomerDB.GridFS() + + if gridFile, errGridFile := gridFS.Open(filename); errGridFile != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to open the web content out of the GridFS!`, filename, errGridFile.Error()) + return + } else { + defer gridFile.Close() + if data, ioError := ioutil.ReadAll(gridFile); ioError != nil { + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to read the web content file.`, filename, ioError.Error()) + return + } else { + zipData = data + } + } + + return +} diff --git a/WebContent/Send.go b/WebContent/Send.go new file mode 100644 index 0000000..c002a6e --- /dev/null +++ b/WebContent/Send.go @@ -0,0 +1,43 @@ +package WebContent + +import "net/http" +import "bytes" +import "errors" +import "fmt" +import "github.com/SommerEngineering/Ocean/MimeTypes" +import "github.com/SommerEngineering/Ocean/Log" +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +func SendContent(response http.ResponseWriter, path string) (err error) { + + content, contError := GetContent(path) + if contError != nil { + err = errors.New("Was not able to read the needed content: " + contError.Error()) + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameREAD, `Was not able to read the needed content.`, contError.Error(), path) + return + } + + contentLength := len(content) + contentType, typeError := MimeTypes.DetectType(path) + + if typeError != nil { + err = errors.New("Was not able to detect the MIME type: " + typeError.Error()) + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameANALYSIS, `Was not able to detect the MIME type for the file.`, path, typeError.Error()) + return + } + + response.Header().Add("Content-Length", fmt.Sprintf("%d", contentLength)) + response.Header().Add("Content-Type", contentType.MimeType) + response.WriteHeader(http.StatusOK) + + buffer := bytes.NewBuffer(content) + _, writeError := buffer.WriteTo(response) + + if writeError != nil { + err = errors.New("Was not able to write the data to the net: " + writeError.Error()) + Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameWRITE, `Was not able to write the file to the browser.`, path, writeError.Error()) + return + } + + return +} diff --git a/WebContent/Variables.go b/WebContent/Variables.go new file mode 100644 index 0000000..4b6b891 --- /dev/null +++ b/WebContent/Variables.go @@ -0,0 +1,10 @@ +package WebContent + +import LM "github.com/SommerEngineering/Ocean/Log/Meta" + +var ( + isInit = false + filename = "" + zipData []byte = nil + senderName LM.Sender = `System::WebContent` +)