2015-10-02 16:29:09 -07:00
Imports System . Threading
Imports System . Globalization
Imports System . Data . SqlClient
Imports System . IO
Imports MySql . Data . MySqlClient
2016-12-25 14:23:08 -08:00
Imports Nest
2016-12-25 16:08:24 -08:00
Imports Newtonsoft . Json
2015-10-02 16:29:09 -07:00
Public Class EventLogLoaderService
2016-12-25 16:08:24 -08:00
Dim ListOfProcessors As List ( Of EventLogProcessor ) = New List ( Of EventLogProcessor )
2015-10-02 16:29:09 -07:00
Dim ArrayThread ( ) As Threading . Thread
2016-12-25 13:12:43 -08:00
Dim ConfigSettingObj As ConfigSetting = New ConfigSetting
2016-12-25 16:08:24 -08:00
2015-10-02 16:29:09 -07:00
Dim ConnectionString As String
Dim DBType As String
Dim ItIsMSSQL As Boolean = False
Dim ItIsMySQL As Boolean = False
2016-12-25 14:23:08 -08:00
Dim ItIsES As Boolean = False
2015-10-02 16:29:09 -07:00
Public SleepTime As Integer = 60 * 1000 ' 1 м и н у т а
2016-12-25 13:12:43 -08:00
Function LoadConfigSetting ( )
2015-10-02 16:29:09 -07:00
2016-12-25 13:12:43 -08:00
LoadConfigSetting = False
2015-10-02 16:29:09 -07:00
Try
2016-12-25 13:12:43 -08:00
Dim PathConfigFile = Path . Combine ( My . Application . Info . DirectoryPath , "config.json" )
If My . Computer . FileSystem . FileExists ( PathConfigFile ) Then
2015-10-02 16:29:09 -07:00
2016-12-25 13:12:43 -08:00
ConfigSettingObj = ConfigSettingsModule . LoadConfigSettingFromFile ( PathConfigFile )
2015-10-02 16:29:09 -07:00
2016-12-25 13:12:43 -08:00
ConnectionString = ConfigSettingObj . ConnectionString
2015-10-02 16:29:09 -07:00
2016-12-25 13:12:43 -08:00
DBType = ConfigSettingObj . DBType
If DBType = "MySQL" Then
ItIsMySQL = True
ElseIf DBType = "MS SQL Server" Then
ItIsMSSQL = True
2016-12-25 14:23:08 -08:00
ElseIf DBType = "ElasticSearch" Then
ItIsES = True
2015-10-02 16:29:09 -07:00
End If
2016-12-25 13:12:43 -08:00
Dim s = ConfigSettingObj . RepeatTime
SleepTime = s * 1000
2015-10-02 16:29:09 -07:00
2016-12-25 13:12:43 -08:00
For Each IBConfig In ConfigSettingObj . Infobases
2015-10-02 16:29:09 -07:00
2016-12-25 13:12:43 -08:00
Dim IB_ID = IBConfig . DatabaseID
Dim IB_Name = IBConfig . DatabaseName
Dim IB_Catalog = IBConfig . DatabaseCatalog
2015-10-02 16:29:09 -07:00
2016-12-25 16:08:24 -08:00
Dim EventLogProcessorObj = New EventLogProcessor
EventLogProcessorObj . Log = NLog . LogManager . GetLogger ( "CurrentThread" )
EventLogProcessorObj . InfobaseGuid = IB_ID
EventLogProcessorObj . InfobaseName = IB_Name
EventLogProcessorObj . Catalog = IB_Catalog
EventLogProcessorObj . ConnectionString = ConnectionString
EventLogProcessorObj . SleepTime = SleepTime
EventLogProcessorObj . ItIsMSSQL = ItIsMSSQL
EventLogProcessorObj . ItIsMySQL = ItIsMySQL
EventLogProcessorObj . ItIsES = ItIsES
2016-12-28 12:04:30 -08:00
EventLogProcessorObj . ESIndexName = ConfigSettingObj . ESIndexName
2017-04-15 16:37:38 -07:00
EventLogProcessorObj . ESIndexPostfix = ConfigSettingObj . ESUseIndexPostfix
2016-12-28 12:04:30 -08:00
EventLogProcessorObj . ESServerName = IBConfig . ESServerName
2017-02-19 14:02:55 -08:00
EventLogProcessorObj . ESUseSynonymsForFieldsNames = ConfigSettingObj . ESUseSynonymsForFieldsNames
EventLogProcessorObj . ESFieldSynonyms = ConfigSettingObj . ESFieldSynonyms
2016-12-25 16:08:24 -08:00
2017-02-20 14:32:07 -08:00
Try
EventLogProcessorObj . LoadEventsStartingAt = Date . Parse ( IBConfig . StartDate )
Catch ex As Exception
EventLogProcessorObj . LoadEventsStartingAt = New Date ( 1900 , 1 , 1 )
End Try
2016-12-25 16:08:24 -08:00
ListOfProcessors . Add ( EventLogProcessorObj )
2015-10-02 16:29:09 -07:00
2016-12-25 16:08:24 -08:00
Next
2015-10-02 16:29:09 -07:00
2016-12-25 13:12:43 -08:00
LoadConfigSetting = True
2015-10-02 16:29:09 -07:00
Else
2016-12-25 16:08:24 -08:00
Log . Error ( "File config.json was not found!" )
2015-10-02 16:29:09 -07:00
End If
Catch ex As Exception
2017-04-15 16:37:38 -07:00
Log . Error ( ex , "Parameters cannot be loaded from config.json (it may be corrupted)" )
2015-10-02 16:29:09 -07:00
End Try
End Function
Private WorkerThread As Thread
Private Log As NLog . Logger
Protected Overrides Sub OnStart ( ByVal args ( ) As String )
' Д о б а в ь т е з д е с ь к о д з а п у с к а с л у ж б ы . Э т о т м е т о д д о л ж е н н а с т р о и т ь в с е н е о б х о д и м о е
' д л я у с п е ш н о й р а б о т ы с л у ж б ы .
SubStart ( )
End Sub
Protected Overrides Sub OnStop ( )
' Д о б а в ь т е з д е с ь к о д д л я з а в е р ш а ю щ и х о п е р а ц и й п е р е д о с т а н о в к о й с л у ж б ы .
SubStop ( )
End Sub
Public Sub SubStart ( )
WorkerThread . Start ( )
End Sub
Public Sub SubStop ( )
WorkerThread . Abort ( )
End Sub
Public Sub New ( )
' Э т о т в ы з о в я в л я е т с я о б я з а т е л ь н ы м д л я к о н с т р у к т о р а .
InitializeComponent ( )
' Д о б а в ь т е в с е и н и ц и а л и з и р у ю щ и е д е й с т в и я п о с л е в ы з о в а InitializeComponent ( ) .
' Н у ж н о д л я т о г о , ч т о с л у ж б а с о з д а в а л а л о г и в с в о е м к а т а л о г е
Directory . SetCurrentDirectory ( My . Application . Info . DirectoryPath )
Log = NLog . LogManager . GetLogger ( "CurrentThread" )
' Log . Info ( "Создание объекта сервиса" )
WorkerThread = New Threading . Thread ( AddressOf DoWork )
WorkerThread . SetApartmentState ( Threading . ApartmentState . STA )
End Sub
Private Sub DoWork ( )
' Log . Info ( "Запуск основного функционала" )
2016-12-25 13:12:43 -08:00
If Not LoadConfigSetting ( ) Then
2016-12-25 16:08:24 -08:00
Log . Error ( "Error while working with config.json file in application folder" )
2015-10-02 16:29:09 -07:00
Environment . Exit ( - 1 )
End If
CreateTables ( )
Dim i = 0
2016-12-25 16:08:24 -08:00
For Each IB In ListOfProcessors
2015-10-02 16:29:09 -07:00
Try
2016-12-25 16:08:24 -08:00
IB . GetInfobaseIDFromDatabase ( )
2015-10-02 16:29:09 -07:00
Catch ex As Exception
2017-01-10 20:54:33 -08:00
Log . Error ( ex , "Error occurred while getting infobase ID from target database (" + IB . InfobaseName + ")" )
2015-10-02 16:29:09 -07:00
Continue For
End Try
Try
Dim Thead = New Threading . Thread ( New ParameterizedThreadStart ( AddressOf IB . DoWork ) )
Thead . IsBackground = True
Thead . Start ( )
ReDim Preserve ArrayThread ( i )
ArrayThread ( i ) = Thead
i = i + 1
Catch ex As Exception
2017-01-10 20:54:33 -08:00
Log . Error ( ex , "Error occurred while starting new thread (" + IB . InfobaseName + ")" )
2015-10-02 16:29:09 -07:00
End Try
Next
End Sub
Sub CreateTables ( )
Try
If ItIsMSSQL Then
Dim objConn As New SqlConnection ( ConnectionString )
objConn . Open ( )
2016-12-25 14:23:08 -08:00
Dim command As New SqlCommand ( "IF NOT EXISTS (select * from sysobjects where id = object_id(N'Events'))
BEGIN
CREATE TABLE [ dbo ] . [ Events ] ( [ InfobaseCode ] int Not NULL , [ DateTime ] [ datetime ] Not NULL ,
2017-01-09 21:09:32 -08:00
[TransactionStatus] [ varchar ] ( 1 ) NULL ,
2016-12-25 14:23:08 -08:00
[TransactionStartTime] [ datetime ] NULL ,
[TransactionMark] bigint NULL ,
2017-01-09 21:09:32 -08:00
[Transaction] [ varchar ] ( 100 ) NULL ,
2016-12-25 14:23:08 -08:00
[UserName] int NULL ,
[ComputerName] int NULL ,
[AppName] Int NULL ,
[EventID] int NULL ,
2017-01-09 21:09:32 -08:00
[EventType] [ varchar ] ( 1 ) NULL ,
[Comment] [ nvarchar ] ( max ) NULL ,
2016-12-25 14:23:08 -08:00
[MetadataID] int NULL ,
2017-01-09 21:09:32 -08:00
[DataStructure] [ nvarchar ] ( max ) NULL ,
[DataString] [ nvarchar ] ( max ) NULL ,
2016-12-25 14:23:08 -08:00
[ServerID] int NULL ,
[MainPortID] int NULL ,
[SecondPortID] int NULL ,
2017-02-03 11:06:17 +05:00
[Seance] int NULL ) ;
2016-12-25 14:23:08 -08:00
CREATE CLUSTERED INDEX [ CIX_Events ] ON [ dbo ] . [ Events ] ( [ InfobaseCode ] , [ DateTime ] )
END ", objConn)
2015-10-02 16:29:09 -07:00
command . ExecuteNonQuery ( )
' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2016-12-25 16:08:24 -08:00
' command . CommandText = "IF NOT EXISTS (select * from sysobjects where id = object_id(N'Params'))" + vbNewLine + _
' " CREATE TABLE [dbo].[Params] ([InfobaseCode] int NOT NULL, [Position] bigint, [Filename] [char](100), [LastEventID] int);" + vbNewLine + _
' " IF NOT EXISTS (select * from sys.indexes where object_id = object_id(N'Params') AND Name = 'ClusteredIndex')" + vbNewLine + _
' " CREATE UNIQUE CLUSTERED INDEX [ClusteredIndex] ON [dbo].[Params] ([InfobaseCode] ASC);"
' command . ExecuteNonQuery ( )
2015-10-02 16:29:09 -07:00
' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2016-12-25 16:08:24 -08:00
command . CommandText = "IF NOT EXISTS (select * from sysobjects where id = object_id(N'Infobases'))" + vbNewLine +
" CREATE TABLE [dbo].[Infobases] ([Guid] [char](40) NOT NULL, [Code] int NOT NULL, [Name] [char](100))" + vbNewLine +
" IF NOT EXISTS (select * from sys.indexes where object_id = object_id(N'Infobases') AND Name = 'ClusteredIndex')" + vbNewLine +
2015-10-02 16:29:09 -07:00
" CREATE UNIQUE CLUSTERED INDEX [ClusteredIndex] ON [dbo].[Infobases] ([Guid] ASC);"
command . ExecuteNonQuery ( )
' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
command . CommandText =
2016-12-25 16:08:24 -08:00
"IF NOT EXISTS (select * from sysobjects where id = object_id(N'Users'))" + vbNewLine +
2017-01-10 22:46:12 -08:00
"CREATE TABLE [dbo].[Users]([InfobaseCode] int NOT NULL, [Code] int NOT NULL, [Name] [nvarchar](100), [Guid] [varchar](40));" + vbNewLine +
2016-12-25 16:08:24 -08:00
"IF NOT EXISTS (select * from sys.indexes where object_id = object_id(N'Users') AND Name = 'ClusteredIndex')" + vbNewLine +
"CREATE UNIQUE CLUSTERED INDEX [ClusteredIndex] ON [dbo].[Users] ([InfobaseCode] ASC, [Code] ASC);" + vbNewLine +
"" +
"IF NOT EXISTS (select * from sysobjects where id = object_id(N'Metadata'))" + vbNewLine +
2017-01-10 22:46:12 -08:00
"CREATE TABLE [dbo].[Metadata]([InfobaseCode] int NOT NULL, [Code] int NOT NULL, [Name] [nvarchar](100), [Guid] [varchar](40));" + vbNewLine +
2016-12-25 16:08:24 -08:00
"IF NOT EXISTS (select * from sys.indexes where object_id = object_id(N'Metadata') AND Name = 'ClusteredIndex')" + vbNewLine +
"CREATE UNIQUE CLUSTERED INDEX [ClusteredIndex] ON [dbo].[Metadata] ([InfobaseCode] ASC, [Code] ASC);" + vbNewLine +
"" +
"IF NOT EXISTS (select * from sysobjects where id = object_id(N'Computers'))" + vbNewLine +
2017-01-10 22:46:12 -08:00
"CREATE TABLE [dbo].[Computers]([InfobaseCode] int NOT NULL, [Code] int NOT NULL, [Name] [nvarchar](100));" + vbNewLine +
2016-12-25 16:08:24 -08:00
"IF NOT EXISTS (select * from sys.indexes where object_id = object_id(N'Computers') AND Name = 'ClusteredIndex')" + vbNewLine +
"CREATE UNIQUE CLUSTERED INDEX [ClusteredIndex] ON [dbo].[Computers] ([InfobaseCode] ASC, [Code] ASC);" + vbNewLine +
"" +
"IF NOT EXISTS (select * from sysobjects where id = object_id(N'Applications'))" + vbNewLine +
2017-01-10 22:46:12 -08:00
"CREATE TABLE [dbo].[Applications]([InfobaseCode] int NOT NULL, [Code] int NOT NULL,[Name] [nvarchar](100));" + vbNewLine +
2016-12-25 16:08:24 -08:00
"IF NOT EXISTS (select * from sys.indexes where object_id = object_id(N'Applications') AND Name = 'ClusteredIndex')" + vbNewLine +
"CREATE UNIQUE CLUSTERED INDEX [ClusteredIndex] ON [dbo].[Applications] ([InfobaseCode] ASC, [Code] ASC);" + vbNewLine +
"" +
"IF NOT EXISTS (select * from sysobjects where id = object_id(N'EventsType'))" + vbNewLine +
2017-01-10 22:46:12 -08:00
"CREATE TABLE [dbo].[EventsType]([InfobaseCode] int NOT NULL, [Code] int NOT NULL, [Name] [nvarchar](max));" + vbNewLine +
2016-12-25 16:08:24 -08:00
"IF NOT EXISTS (select * from sys.indexes where object_id = object_id(N'EventsType') AND Name = 'ClusteredIndex')" + vbNewLine +
"CREATE UNIQUE CLUSTERED INDEX [ClusteredIndex] ON [dbo].[EventsType] ([InfobaseCode] ASC, [Code] ASC);" + vbNewLine +
"" +
"IF NOT EXISTS (select * from sysobjects where id = object_id(N'Servers'))" + vbNewLine +
2017-01-10 22:46:12 -08:00
"CREATE TABLE [dbo].[Servers]([InfobaseCode] int NOT NULL, [Code] int NOT NULL, [Name] [nvarchar](100));" + vbNewLine +
2016-12-25 16:08:24 -08:00
"IF NOT EXISTS (select * from sys.indexes where object_id = object_id(N'Servers') AND Name = 'ClusteredIndex')" + vbNewLine +
"CREATE UNIQUE CLUSTERED INDEX [ClusteredIndex] ON [dbo].[Servers] ([InfobaseCode] ASC, [Code] ASC);" + vbNewLine +
"" +
"IF NOT EXISTS (select * from sysobjects where id = object_id(N'MainPorts'))" + vbNewLine +
2017-01-10 22:46:12 -08:00
"CREATE TABLE [dbo].[MainPorts]([InfobaseCode] int NOT NULL, [Code] int NOT NULL, [Name] [nvarchar](100));" + vbNewLine +
2016-12-25 16:08:24 -08:00
"IF NOT EXISTS (select * from sys.indexes where object_id = object_id(N'MainPorts') AND Name = 'ClusteredIndex')" + vbNewLine +
"CREATE UNIQUE CLUSTERED INDEX [ClusteredIndex] ON [dbo].[MainPorts] ([InfobaseCode] ASC, [Code] ASC);" + vbNewLine +
"" +
"IF NOT EXISTS (select * from sysobjects where id = object_id(N'SecondPorts'))" + vbNewLine +
2017-01-10 22:46:12 -08:00
"CREATE TABLE [dbo].[SecondPorts]([InfobaseCode] int NOT NULL, [Code] int NOT NULL, [Name] [nvarchar](100));" + vbNewLine +
2016-12-25 16:08:24 -08:00
"IF NOT EXISTS (select * from sys.indexes where object_id = object_id(N'SecondPorts') AND Name = 'ClusteredIndex')" + vbNewLine +
2015-10-02 16:29:09 -07:00
"CREATE UNIQUE CLUSTERED INDEX [ClusteredIndex] ON [dbo].[SecondPorts] ([InfobaseCode] ASC, [Code] ASC);"
command . ExecuteNonQuery ( )
' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
command . CommandText = "SELECT TOP 1 * FROM Events"
command . ExecuteReader ( )
command . Dispose ( )
objConn . Close ( )
2016-12-25 17:15:40 -08:00
objConn . Dispose ( )
2015-10-02 16:29:09 -07:00
ElseIf ItIsMySQL Then
Dim objConn = New MySql . Data . MySqlClient . MySqlConnection ( ConnectionString )
objConn . Open ( )
Dim DBName = objConn . Database
Dim command = New MySql . Data . MySqlClient . MySqlCommand
command . Connection = objConn
2016-12-25 16:08:24 -08:00
command . CommandText = "CREATE TABLE IF NOT EXISTS `Events` (`InfobaseCode` int(11) NOT NULL, `DateTime` int(11) NOT NULL," +
"`TransactionStatus` varchar(1) NULL, `TransactionStartTime` datetime NULL, " +
"`TransactionMark` bigint NULL, `Transaction` varchar(100) NULL, `UserName` int(11) NULL, `ComputerName` int(11) NULL, " +
2017-02-03 11:06:17 +05:00
"`AppName` int(11) NULL, `EventID` int(11) NULL, `EventType` varchar(1) NULL, " +
2017-01-09 21:09:32 -08:00
"`Comment` text NULL, `MetadataID` int(11) NULL, `DataStructure` text NULL, `DataString` text NULL, " +
2017-02-03 11:06:17 +05:00
"`ServerID` int(11) NULL, `MainPortID` int(11) NULL, `SecondPortID` int(11) NULL, `Seance` int(11) NULL" +
2015-10-02 16:29:09 -07:00
") ENGINE=InnoDB DEFAULT CHARSET=utf8;"
command . ExecuteNonQuery ( )
' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2016-12-25 16:08:24 -08:00
' command . CommandText = "CREATE TABLE IF NOT EXISTS `Params` (`InfobaseCode` int(11) NOT NULL, `Position` bigint, `Filename` varchar(100)," + _
' "`LastEventID` int(11), PRIMARY KEY `InfobaseCode` (`InfobaseCode`));"
' command . ExecuteNonQuery ( )
2015-10-02 16:29:09 -07:00
' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2016-12-25 16:08:24 -08:00
command . CommandText = "CREATE TABLE IF NOT EXISTS `Infobases` (`Guid` varchar(40) NOT NULL, `Code` int(11) NOT NULL, `Name` varchar(100)," +
2015-10-02 16:29:09 -07:00
"PRIMARY KEY `Guid` (`Guid`));"
command . ExecuteNonQuery ( )
' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
command . CommandText =
2016-12-25 16:08:24 -08:00
"CREATE TABLE IF NOT EXISTS `Users`(`InfobaseCode` int(11) NOT NULL, `Code` int(11) NOT NULL, `Name` varchar(100), `Guid` varchar(40), PRIMARY KEY (`InfobaseCode`, `Code`));" + vbNewLine +
"" +
"CREATE TABLE IF NOT EXISTS `Metadata`(`InfobaseCode` int(11) NOT NULL, `Code` int(11) NOT NULL, `Name` varchar(100), `Guid` varchar(40), PRIMARY KEY (`InfobaseCode`, `Code`));" + vbNewLine +
"" +
"CREATE TABLE IF NOT EXISTS `Computers`(`InfobaseCode` int(11) NOT NULL, `Code` int(11) NOT NULL, `Name` varchar(100), PRIMARY KEY (`InfobaseCode`, `Code`));" + vbNewLine +
"" +
"CREATE TABLE IF NOT EXISTS `Applications`(`InfobaseCode` int(11) NOT NULL, `Code` int(11) NOT NULL, `Name` varchar(100), PRIMARY KEY (`InfobaseCode`, `Code`));" + vbNewLine +
"" +
2017-01-10 22:46:12 -08:00
"CREATE TABLE IF NOT EXISTS `EventsType`(`InfobaseCode` int(11) NOT NULL, `Code` int(11) NOT NULL, `Name` text, PRIMARY KEY (`InfobaseCode`, `Code`));" + vbNewLine +
2016-12-25 16:08:24 -08:00
"" +
"CREATE TABLE IF NOT EXISTS `Servers`(`InfobaseCode` int(11) NOT NULL, `Code` int(11) NOT NULL, `Name` varchar(100), PRIMARY KEY (`InfobaseCode`, `Code`));" + vbNewLine +
"" +
"CREATE TABLE IF NOT EXISTS `MainPorts`(`InfobaseCode` int(11) NOT NULL, `Code` int(11) NOT NULL, `Name` varchar(100), PRIMARY KEY (`InfobaseCode`, `Code`));" + vbNewLine +
"" +
2015-10-02 16:29:09 -07:00
"CREATE TABLE IF NOT EXISTS `SecondPorts`(`InfobaseCode` int(11) NOT NULL, `Code` int(11) NOT NULL, `Name` varchar(100), PRIMARY KEY (`InfobaseCode`, `Code`));"
command . ExecuteNonQuery ( )
' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
command . Dispose ( )
objConn . Close ( )
2016-12-25 17:15:40 -08:00
objConn . Dispose ( )
2015-10-02 16:29:09 -07:00
End If
2016-12-25 16:08:24 -08:00
Log . Info ( "Target database tables have been verified!" )
2015-10-02 16:29:09 -07:00
Catch ex As Exception
2017-01-10 20:54:33 -08:00
Log . Error ( ex , "Error occurred while during target database tables verification" )
2015-10-02 16:29:09 -07:00
End Try
End Sub
End Class