Imports System.IO Imports System.ServiceModel Imports DigitalData.Modules.EDMI.API.Constants Imports DigitalData.Modules.EDMI.API.EDMIServiceReference Imports DigitalData.Modules.EDMI.API.Rights Imports DigitalData.Modules.Language.Utils Imports DigitalData.Modules.Logging Public Class Client Public Const INVALID_OBEJCT_ID As Long = 0 Private Const KIND_TYPE_DOC = "DOC" Private ReadOnly _logger As Logger Private ReadOnly _channelFactory As ChannelFactory(Of IEDMIServiceChannel) Private ReadOnly _IPAddressServer As String Private _dummy_table_attributes As DataTable Private _channel As IEDMIServiceChannel Private _FileEx As FileSystem.File ''' ''' Creates a new EDMI Client object ''' ''' LogConfig object ''' The IP address/hostname and port, separated by semicolon or colon, ex localhost:9000 Public Sub New(LogConfig As LogConfig, ServiceAdress As String) _logger = LogConfig.GetLogger() _FileEx = New Filesystem.File(LogConfig) Dim oServiceAddress As String = ServiceAdress Dim oAddressArray() As String If oServiceAddress.Contains(";") Then oAddressArray = oServiceAddress.Split(";") Else oAddressArray = oServiceAddress.Split(":") End If Try _IPAddressServer = oAddressArray(0) Dim oBinding = Channel.GetBinding() Dim oAddress = New EndpointAddress($"net.tcp://{oAddressArray(0)}:{oAddressArray(1)}/DigitalData/Services/Main") Dim oFactory = New ChannelFactory(Of IEDMIServiceChannel)(oBinding, oAddress) _channelFactory = oFactory Catch ex As Exception _logger.Error(ex) End Try End Sub ''' ''' Creates a new EDMI Client object ''' ''' LogConfig object ''' The IP address to connect to ''' The Port number to use for the connection Public Sub New(LogConfig As LogConfig, IPAddress As String, PortNumber As Integer) _logger = LogConfig.GetLogger() _FileEx = New Filesystem.File(LogConfig) Try _IPAddressServer = IPAddress Dim oBinding = Channel.GetBinding() Dim oAddress = New EndpointAddress($"net.tcp://{IPAddress}:{PortNumber}/DigitalData/Services/Main") Dim oFactory = New ChannelFactory(Of IEDMIServiceChannel)(oBinding, oAddress) _channelFactory = oFactory Catch ex As Exception _logger.Error(ex) End Try End Sub ''' ''' Parse a IPAddress:Port String into its parts ''' ''' ''' Public Shared Function ParseServiceAddress(AddressWithOptionalPort As String) As Tuple(Of String, Integer) Dim oSplit() As String = AddressWithOptionalPort.Split(":"c) Dim oAppServerAddress As String = oSplit(0) Dim oAppServerPort As Integer If oSplit.Length = 2 Then If Integer.TryParse(oSplit(1), oAppServerPort) Then oAppServerPort = DEFAULT_SERVICE_PORT End If Else oAppServerPort = DEFAULT_SERVICE_PORT End If Return New Tuple(Of String, Integer)(oAppServerAddress, oAppServerPort) End Function ''' ''' Connect to the service ''' ''' True if connection was successful, false otherwise Public Function Connect() As Boolean Try _channel = GetChannel() _logger.Debug("Opening channel..") _channel.Open() _logger.Info($"Connection to AppService {_IPAddressServer} successfully established!") Return True Catch ex As Exception _logger.Error(ex) Return False End Try End Function ''' ''' Imports a file from a filepath, creating a IDB ObjectId and Filesystem Object ''' ''' Type of ObjectStore. Can be WORK or ARCHIVE. ''' Business entity that the new file object should belong to. ''' Other file import options ''' When local filepath was not found ''' When there was a error in the Service ''' The ObjectId of the newly generated filesystem object Public Async Function NewFileAsync(pFilePath As String, pObjectStoreName As String, pObjectKind As String, pBusinessEntity As String, Optional pImportOptions As NewFileOptions = Nothing) As Task(Of Long) Try ' Set default options If pImportOptions Is Nothing Then pImportOptions = New NewFileOptions() End If ' Check if file exists If File.Exists(pFilePath) = False Then Throw New FileNotFoundException("Path does not exist") End If Dim oFileInfo As New FileInfo(pFilePath) Dim oExtension As String = oFileInfo.Extension Dim oFileName As String = oFileInfo.Name Dim oFileCreatedAt As Date = oFileInfo?.CreationTime Dim oFileModifiedAt As Date = oFileInfo?.LastWriteTime Dim oFileHash As String = _FileEx.GetChecksum(oFileInfo.FullName) ' Importing the file now Using oFileStream As New FileStream(pFilePath, FileMode.Open, FileAccess.Read) Using oMemoryStream As New MemoryStream() oFileStream.CopyTo(oMemoryStream) Dim oContents = oMemoryStream.ToArray() Dim oFileImportResponse = Await _channel.NewFileAsync(New NewFileRequest With { .BusinessEntity = pBusinessEntity, .File = New FileProperties With { .FileName = oFileInfo.Name, .FileCreatedAt = oFileCreatedAt, .FileChangedAt = oFileModifiedAt, .FileContents = oContents, .FileImportedAt = pImportOptions.DateImported, .FileChecksum = oFileHash }, .KindType = pObjectKind, .StoreName = pObjectStoreName, .Who = pImportOptions.Username }) If oFileImportResponse.OK = False Then Throw New ApplicationException("Could not Import File Contents!") End If Return oFileImportResponse.ObjectId End Using End Using Catch ex As Exception _logger.Error(ex) Return INVALID_OBEJCT_ID End Try End Function ''' ''' Sets a value to an attribute ''' ''' IDB ObjectId ''' Name of the attribute ''' ''' ''' Public Function SetVariableValue(pObjectId As Long, pAttributeName As String, pValue As Object, Optional pOptions As SetVariableValueOptions = Nothing) As Boolean Try ' Set default options If pOptions Is Nothing Then pOptions = New SetVariableValueOptions() End If Dim oLanguage = GetUserLanguage(pOptions.UserLanguage) Dim oUsername = GetUserName(pOptions.UserName) Dim oOptions As New GetVariableValueOptions With { .UserLanguage = oLanguage, .UserName = oUsername } Try Dim oResponse = _channel.TestObjectIdExists(New TestObjectIdExistsRequest With {.ObjectId = pObjectId}) If oResponse.Exists = False Then Return False End If Catch ex As Exception _logger.Error(ex) Return False End Try Dim oType = pValue.GetType.Name If oType = GetType(DataTable).Name Then Dim oValueTable As DataTable = pValue Dim oCurrentValue As VariableValue ' Get current value oCurrentValue = GetVariableValue(pObjectId, pAttributeName, oOptions) ' If current value is datatable If oCurrentValue.Type = GetType(DataTable) Then ' Convert value to Datatable Dim oTable As DataTable = oCurrentValue.Value If oTable.Rows.Count > 1 Then 'now Checking whether the old row still remains in Vector? If not it will be deleted as it cannot be replaced in multivalues For Each oRow As DataRow In oTable.Rows Dim oExists As Boolean = False For Each oNewValueRow As DataRow In oValueTable.Rows _logger.Debug($"Checking oldValue[{oCurrentValue}] vs NewValue [{oNewValueRow.Item(1)}]") If oNewValueRow.Item(1).ToString.ToUpper = oRow.Item(0).ToString.ToUpper Then oExists = True Exit For End If Next If oExists = False Then _logger.Debug($"Value [{oRow.Item(0)}] no longer existing in Vector-Attribute [{pAttributeName}] - will be deleted!") DeleteTermObjectFromMetadata(pObjectId, pAttributeName, oRow.Item(0)) End If Next End If Else If oValueTable.Rows.Count > 1 Then 'now Checking whether the old row still remains in Vector? If not it will be deleted as it cannot be replaced in multivalues Dim oExists As Boolean = False For Each oNewValueRow As DataRow In oValueTable.Rows _logger.Debug($"Checking oldValue[{oCurrentValue}] vs NewValue [{oNewValueRow.Item(1)}]") If oNewValueRow.Item(1).ToString.ToUpper = oCurrentValue.ToString.ToUpper Then oExists = True Exit For End If Next If oExists = False Then _logger.Debug($"Value [{oCurrentValue}] no longer existing in Vector-Attribute [{pAttributeName}] - will be deleted!") DeleteTermObjectFromMetadata(pObjectId, pAttributeName, oCurrentValue.Value) End If Else _logger.Debug($"Value [{oCurrentValue}] of Attribute [{pAttributeName}] obviously was updated during runtime - will be deleted!") DeleteTermObjectFromMetadata(pObjectId, pAttributeName, oCurrentValue.Value) End If End If For Each oNewValueRow As DataRow In oValueTable.Rows Dim oResult As Boolean = NewObjectData(pObjectId, pAttributeName, pValue, New NewObjectOptions With { .UserLanguage = oLanguage, .UserName = oUsername }) If oResult = False Then Return False End If Next Return True Else Return NewObjectData(pObjectId, pAttributeName, pValue, New NewObjectOptions With { .UserLanguage = oLanguage, .UserName = oUsername }) End If Catch ex As Exception _logger.Error(ex) Return False End Try End Function ''' ''' ''' ''' ''' ''' ''' Public Function GetVariableValue(pObjectId As Long, pAttributeName As String, Optional pOptions As GetVariableValueOptions = Nothing) As VariableValue If pOptions Is Nothing Then pOptions = New GetVariableValueOptions() End If pOptions.UserLanguage = GetUserLanguage(pOptions.UserLanguage) pOptions.UserName = GetUserName(pOptions.UserName) ' Check if ObjectId exists Try Dim oResponse = _channel.TestObjectIdExists(New TestObjectIdExistsRequest With {.ObjectId = pObjectId}) If oResponse.Exists = False Then Return New VariableValue() End If Catch ex As Exception _logger.Error(ex) Return New VariableValue() End Try Try ' Get Attributes and Values from Database Dim oAttributes As List(Of ObjectAttribute) = GetAttributesForObject(pObjectId, pOptions.UserLanguage) If oAttributes Is Nothing Then Return New VariableValue() End If Dim oValues = oAttributes.AsEnumerable(). Where(Function(pAttr) Return pAttr.Title.ToUpper = pAttributeName.ToUpper End Function). Select(Function(pAttr) pAttr.Value). ToList() ' Either return a list or a single value or nothing, always wrapped in VariableValue If oValues.Count > 1 Then Return New VariableValue(oValues) ElseIf oValues.Count = 1 Then Return New VariableValue(oValues.First()) Else Return New VariableValue() End If Catch ex As Exception _logger.Error(ex) Return New VariableValue() End Try End Function Private Function GetValueByType(pRow As DataRow, pTypeString As String) As Object Try Dim oAttributeValue As Object Select Case pTypeString Case Constants.AttributeTypeName.BIT oAttributeValue = pRow.Item("ValueBigInt") Case Constants.AttributeTypeName.BIG_INTEGER oAttributeValue = pRow.Item("ValueBigInt") Case Constants.AttributeTypeName.DATE oAttributeValue = pRow.Item("ValueDate") Case Constants.AttributeTypeName.DATETIME oAttributeValue = pRow.Item("ValueDate") Case Constants.AttributeTypeName.DECIMAL oAttributeValue = pRow.Item("ValueDecimal") Case Constants.AttributeTypeName.FLOAT oAttributeValue = pRow.Item("ValueDecimal") Case Constants.AttributeTypeName.VARCHAR oAttributeValue = pRow.Item("ValueText") Case Constants.AttributeTypeName.VECTOR_INTEGER oAttributeValue = pRow.Item("ValueBigInt") Case Constants.AttributeTypeName.VECTOR_STRING oAttributeValue = pRow.Item("ValueText") Case Else oAttributeValue = Nothing End Select Return oAttributeValue Catch ex As Exception _logger.Error(ex) Return Nothing End Try End Function Private Function GetAttributesForObject(pObjectId As Long, pLanguage As String) As List(Of ObjectAttribute) Dim oAttributes As New List(Of ObjectAttribute) Try Dim oResult As TableResult = _channel.ReturnDatatable_MSSQL_IDB($"EXEC [PRIDB_GET_VALUE_DT] {pObjectId}, '{pLanguage}'") If oResult.OK = False Then Throw New ApplicationException(oResult.ErrorMessage) End If If oResult.Table Is Nothing OrElse oResult.Table.Rows.Count = 0 Then Return Nothing End If For Each oRow As DataRow In oResult.Table.Rows Dim oAttribute As New ObjectAttribute With { .Id = oRow.Item("AttributeId"), .Title = oRow.Item("AttributeTitle"), .Type = oRow.Item("AttributeType"), .ValueBigInt = NotNull(oRow.Item("ValueBigInt"), Nothing), .ValueDate = NotNull(oRow.Item("ValueDate"), Nothing), .ValueDecimal = NotNull(oRow.Item("ValueDecimal"), Nothing), .ValueText = NotNull(oRow.Item("ValueText"), Nothing) } oAttributes.Add(oAttribute) Next Return oAttributes Catch ex As Exception _logger.Error(ex) Return Nothing End Try End Function Private Function NewObjectData(pObjectId As Long, pAttributeName As String, pValue As Object, pOptions As NewObjectOptions) If pOptions Is Nothing Then pOptions = New NewObjectOptions() End If Dim oLanguage = GetUserLanguage(pOptions.UserLanguage) Dim oUsername = GetUserName(pOptions.UserName) Dim oSql = $"DECLARE @NEW_OBJ_MD_ID BIGINT EXEC PRIDB_NEW_OBJ_DATA({pObjectId}, '{pAttributeName}', '{oUsername}', '{pValue}', '{oLanguage}', 0, @OMD_ID = @NEW_OBJ_MD_ID OUTPUT)" Dim oResult = _channel.ExecuteNonQuery_MSSQL_IDB(oSql) If oResult.OK = False Then _logger.Warn("Error while deleting Term object") _logger.Error(oResult.ErrorMessage) End If Return oResult.OK End Function Private Function DeleteTermObjectFromMetadata(pObjectId As Long, pAttributeName As String, pTerm2Delete As String, Optional pUsername As String = "", Optional pLanguage As String = "") As Boolean Try Dim oLanguage = GetUserLanguage(pLanguage) Dim oUsername = GetUserName(pUsername) Dim oIdIsForeign As Integer = 1 Dim oDELSQL = $"EXEC PRIDB_DELETE_TERM_OBJECT_METADATA {pObjectId},'{pAttributeName}','{pTerm2Delete}','{oUsername}','{oLanguage}',{oIdIsForeign}" Dim oResult = _channel.ExecuteNonQuery_MSSQL_IDB(oDELSQL) If oResult.OK = False Then _logger.Warn("Error while deleting Term object") _logger.Error(oResult.ErrorMessage) End If Return oResult.OK Catch ex As Exception _logger.Error(ex) Return False End Try End Function Public Function GetDatatableFromIDB(SQL As String) As TableResult Try Dim oResponse = _channel.ReturnDatatable_MSSQL_IDB(SQL) Return oResponse Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function Public Async Function GetDatatableFromIDBAsync(SQL As String) As Task(Of TableResult) Try Dim oResponse = Await _channel.ReturnDatatable_MSSQL_IDBAsync(SQL) Return oResponse Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function Public Function GetScalarValueFromIDB(SQL As String) As ScalarResult Try Dim oResponse = _channel.ReturnScalar_MSSQL_IDB(SQL) Return oResponse Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function Public Async Function GetScalarValueFromIDBAsync(SQL As String) As Task(Of ScalarResult) Try Dim oResponse = Await _channel.ReturnScalar_MSSQL_IDBAsync(SQL) Return oResponse Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function Public Function GetDatatableByName(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As TableResult Try Dim oResponse = _channel.ReturnDatatableFromCache(DatatableName, FilterExpression, SortByColumn) Return oResponse Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function Public Async Function GetDatatableByNameAsync(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As Task(Of TableResult) Try Dim oResponse = _channel.ReturnDatatableFromCache(DatatableName, FilterExpression, SortByColumn) Return oResponse Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function ''' ''' Return infos about a file object ''' ''' ''' ''' Public Function GetDocumentInfo(UserId As Long, ObjectId As Long) As DocumentInfo Try Dim oResponse As DocumentInfoResponse = _channel.GetFileInfoByObjectId(New DocumentInfoRequest With { .ObjectId = ObjectId, .UserId = UserId }) Return New DocumentInfo With { .AccessRight = oResponse.FileRight, .FullPath = oResponse.FullPath } Catch ex As FaultException(Of ObjectDoesNotExistFault) _logger.Error(ex) Throw ex Catch ex As FaultException _logger.Error(ex) Throw ex Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function Public Async Function GetDocumentInfoAsync(UserId As Long, ObjectId As Long) As Task(Of DocumentInfo) Try Dim oParams = New DocumentInfoRequest With { .ObjectId = ObjectId, .UserId = UserId } Dim oResponse As DocumentInfoResponse = Await _channel.GetFileInfoByObjectIdAsync(oParams) Return New DocumentInfo With { .AccessRight = oResponse.FileRight, .FullPath = oResponse.FullPath } Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function ''' ''' Aborts the channel and creates a new connection ''' Public Sub Reconnect() _logger.Warn("Connection faulted. Trying to reconnect..") Try _channel.Abort() _channel = GetChannel() _channel.Open() Catch ex As Exception _logger.Error(ex) End Try End Sub ''' ''' Creates a channel and adds a Faulted-Handler ''' ''' A channel object Private Function GetChannel() As IEDMIServiceChannel Try _logger.Debug("...Creating channel..") Dim oChannel = _channelFactory.CreateChannel() AddHandler oChannel.Faulted, AddressOf Reconnect Return oChannel Catch ex As Exception _logger.Error(ex) Throw ex End Try End Function Private Function GetUserLanguage(pOverrideLanguage As String) As String Return NotNull(pOverrideLanguage, Threading.Thread.CurrentThread.CurrentUICulture.Name) End Function Private Function GetUserName(pOverrideName) As String Return NotNull(pOverrideName, Environment.UserName) End Function #Region "Option & Parameter Classes" ''' ''' Import options for NewFileAsync. ''' Public Class NewFileOptions ''' ''' Windows username of the user responsible for the import. Defaults to the currently logged in user. ''' Public Property Username As String = Environment.UserName ''' ''' Date when the file was imported. Can be in the past. Defaults to now. ''' Public Property DateImported As Date = Date.Now End Class Public Class NewObjectOptions Public UserName As String Public UserLanguage As String End Class Public Class VariableValueOptions Public UserName As String Public UserLanguage As String End Class Public Class GetVariableValueOptions Inherits VariableValueOptions End Class Public Class SetVariableValueOptions Inherits VariableValueOptions End Class #End Region #Region "Response Classes" Public Class StreamedFile Public Stream As MemoryStream Public FileName As String End Class Public Class FileList Public Datatable As DataTable End Class Public Class DocumentInfo Public FullPath As String Public AccessRight As AccessRight End Class Public Class VariableValue Public ReadOnly Property IsVector As Boolean = False Public Property Value As Object Public Property Type As Type Public Sub New() MyClass.New(Nothing) End Sub Public Sub New(pValue As Object) ' Check if value is a collection If TypeOf pValue Is IEnumerable Then IsVector = True End If ' Try to determine the type If IsNothing(pValue) Then Type = Nothing Else Type = pValue.GetType End If Value = pValue End Sub End Class #End Region Public Class ObjectAttribute Public Property Id As Long Public Property Title As String Public Property Type As String Public Property ValueBigInt As Long Public Property ValueText As String Public Property ValueDecimal As Decimal Public Property ValueDate As DateTime Public ReadOnly Property Value As Object Get Return GetValue() End Get End Property Private Function GetValue() As Object Select Case Type Case AttributeTypeName.VECTOR_INTEGER Return ValueBigInt Case AttributeTypeName.BIG_INTEGER Return ValueBigInt Case AttributeTypeName.VECTOR_STRING Return ValueText Case AttributeTypeName.VARCHAR Return ValueText Case AttributeTypeName.BIT Return IIf(ValueBigInt = 1, True, False) Case AttributeTypeName.DATE Return ValueDate Case AttributeTypeName.DATETIME Return ValueDate Case AttributeTypeName.DECIMAL Return ValueDecimal Case AttributeTypeName.FLOAT Return ValueDecimal Case Else Return Nothing End Select End Function End Class End Class