/// <summary> /// Submits an audit event via the Records365 vNext Connector API. /// </summary> /// <param name="submitContext"></param> /// <returns></returns> public override async Task Submit(SubmitContext submitContext) { // Submit via HTTP API Client that is generated with AutoRest var apiClient = ApiClientFactory.CreateApiClient(submitContext.ApiClientFactorySettings); Func <string, DateTime> parseDateTime = (string value) => { DateTime result; return(!string.IsNullOrEmpty(value) && DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result) ? result : DateTime.UtcNow); }; var auditEventModel = new ConnectorAuditEventModel() { EventExternalId = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.AuditEvent.EventExternalId)?.Value ?? "", ItemExternalId = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.AuditEvent.ExternalId)?.Value ?? "", CreatedDate = parseDateTime(submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.AuditEvent.Created)?.Value), Description = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.AuditEvent.Description)?.Value ?? "", EventType = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.AuditEvent.EventType)?.Value ?? "", UserName = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.AuditEvent.UserName)?.Value ?? "", UserId = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.AuditEvent.UserId)?.Value ?? "", ConnectorId = submitContext.ConnectorConfigId.ToString(), SourceProperties = new List <SubmissionMetaDataModel>(), }; if (submitContext.SourceMetaData != null) { auditEventModel.SourceProperties = submitContext.SourceMetaData; } var shouldContinue = true; try { var result = await GetRetryPolicy(submitContext).ExecuteAsync( async(ct) => { var authHelper = ApiClientFactory.CreateAuthenticationHelper(); var headers = await authHelper.GetHttpRequestHeaders(submitContext.AuthenticationHelperSettings).ConfigureAwait(false); return(await apiClient.ApiAuditEventsPutWithHttpMessagesAsync( auditEventModel, customHeaders: headers, cancellationToken: ct ).ConfigureAwait(false)); }, submitContext.CancellationToken ).ConfigureAwait(false); shouldContinue = await HandleSubmitResponse(submitContext, result, "AuditEvent").ConfigureAwait(false); } catch (HttpOperationException ex) when(ex.Response?.StatusCode == System.Net.HttpStatusCode.Conflict) { // submitted item already exists! Nothing to do but continue with the submission pipeline LogVerbose(submitContext, nameof(Submit), $"Submission returned {ex.Response.StatusCode} : AuditEvent already submitted."); } if (shouldContinue) { await InvokeNext(submitContext).ConfigureAwait(false); } }
public void CreateAuthenticationHelper_WhenCalledFromASingleThread_CreatesAnInstanceOfAuthenticationHelper() { // Arrange var sutApiClientFactory = new ApiClientFactory(); // Act var result = sutApiClientFactory.CreateAuthenticationHelper(); // Assert result.Should().NotBeNull(); result.Should().BeOfType <AuthenticationHelper>(); }
/// <summary> /// Submits an item binary via the Records365 vNext Connector API. /// </summary> /// <param name="submitContext"></param> /// <returns></returns> public override async Task Submit(SubmitContext submitContext) { var binarySubmitContext = submitContext as BinarySubmitContext; ValidateFields(binarySubmitContext); // Submit via HTTP API Client that is generated with AutoRest var apiClient = ApiClientFactory.CreateApiClient(submitContext.ApiClientFactorySettings); var result = await GetRetryPolicy(submitContext).ExecuteAsync( async(ct) => { // In case a stream is reused during submission retry, it might not be in 0 Position // since the previous submission already read to the end. We should reset this value. if (binarySubmitContext.Stream.CanSeek) { binarySubmitContext.Stream.Position = 0; } var authHelper = ApiClientFactory.CreateAuthenticationHelper(); var headers = await authHelper.GetHttpRequestHeaders(submitContext.AuthenticationHelperSettings).ConfigureAwait(false); return(await apiClient.ApiBinariesPostWithHttpMessagesAndStreamAsync( binarySubmitContext.ConnectorConfigId.ToString(), binarySubmitContext.ItemExternalId, binarySubmitContext.ExternalId, binarySubmitContext.FileName, binarySubmitContext.CorrelationId.ToString(), inputStream: binarySubmitContext.Stream, customHeaders: headers, cancellationToken: ct ).ConfigureAwait(false)); }, binarySubmitContext.CancellationToken ).ConfigureAwait(false); var shouldContinue = true; shouldContinue = await HandleSubmitResponse(submitContext, result, "Binary").ConfigureAwait(false); if (shouldContinue) { await InvokeNext(submitContext).ConfigureAwait(false); } }
/// <summary> /// Gets the ConnectorConfigModel from the Records365 Connector API. /// </summary> /// <param name="connectorConfigId"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <ConnectorConfigModel> GetConnectorConfig(Guid connectorConfigId, CancellationToken cancellationToken) { var client = ApiClientFactory.CreateApiClient(ApiClientFactorySettings); var policy = ApiClientRetryPolicy.GetPolicy(4, cancellationToken); var connectorConfig = await policy.ExecuteAsync( async (ct) => { var authHelper = ApiClientFactory.CreateAuthenticationHelper(); var headers = await authHelper.GetHttpRequestHeaders(AuthenticationHelperSettings).ConfigureAwait(false); var response = await client.ApiConnectorConfigurationsByIdGetWithHttpMessagesAsync( connectorConfigId, customHeaders: headers, cancellationToken: ct ).ConfigureAwait(false); return(response.Body); }, cancellationToken ).ConfigureAwait(false); return(connectorConfig); }
public void CreateAuthenticationHelper_WhenCalledInParallel_CreatesASingletonObject() { // Arrange var sutApiClientFactory = new ApiClientFactory(); // Act Func <IAuthenticationHelper> func = () => sutApiClientFactory.CreateAuthenticationHelper(); int parallelTaskCount = 100; Task <IAuthenticationHelper>[] tasks = new Task <IAuthenticationHelper> [parallelTaskCount]; for (int i = 0; i < parallelTaskCount; i++) { tasks[i] = Task.Factory.StartNew <IAuthenticationHelper>(func); } Task.WaitAll(tasks); var results = tasks.Select(t => t.GetAwaiter().GetResult()).ToList(); // Assert results.Count.Should().Be(parallelTaskCount); var firstResult = results.First(); // Make sure that it's a singleton object results.ForEach(result => result.Should().BeSameAs(firstResult)); }
/// <summary> /// Submits an aggregation to the Records365 vNext Connector API. /// </summary> /// <param name="submitContext"></param> /// <returns></returns> public async override Task Submit(SubmitContext submitContext) { // Submit via HTTP API Client that is generated with AutoRest var apiClient = ApiClientFactory.CreateApiClient(submitContext.ApiClientFactorySettings); Func <string, DateTime> parseDateTime = (string value) => { DateTime result; return(!string.IsNullOrEmpty(value) && DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out result) ? result : DateTime.UtcNow); }; var aggregationModel = new AggregationSubmissionInputModel() { ItemTypeId = submitContext.ItemTypeId, ExternalId = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.ExternalId)?.Value ?? "", ParentExternalId = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.ParentExternalId)?.Value ?? "", Title = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.Title)?.Value ?? "", Author = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.Author)?.Value ?? "", SourceLastModifiedDate = parseDateTime(submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.SourceLastModifiedDate)?.Value), SourceCreatedDate = parseDateTime(submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.SourceCreatedDate)?.Value), SourceLastModifiedBy = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.SourceLastModifiedBy)?.Value ?? "", SourceCreatedBy = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.SourceCreatedBy)?.Value ?? "", ConnectorId = submitContext.ConnectorConfigId.ToString(), Location = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.Location)?.Value ?? "", MediaType = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.MediaType)?.Value ?? "Electronic", BarcodeType = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.BarcodeType)?.Value ?? "", BarcodeValue = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.BarcodeValue)?.Value ?? "", RecordCategoryId = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.RecordCategoryID)?.Value ?? "", SecurityProfileIdentifier = submitContext.CoreMetaData?.FirstOrDefault(metadata => metadata.Name == Fields.SecurityProfileIdentifier)?.Value ?? "", SourceProperties = new List <SubmissionMetaDataModel>(), Relationships = new List <RelationshipDataModel>() }; if (submitContext.SourceMetaData != null) { aggregationModel.SourceProperties = submitContext.SourceMetaData; } if (submitContext.Relationships != null) { aggregationModel.Relationships = submitContext.Relationships; } var shouldContinue = true; try { var result = await GetRetryPolicy(submitContext).ExecuteAsync( async(ct) => { var authHelper = ApiClientFactory.CreateAuthenticationHelper(); var headers = await authHelper.GetHttpRequestHeaders(submitContext.AuthenticationHelperSettings).ConfigureAwait(false); return(await apiClient.ApiAggregationsPostWithHttpMessagesAsync( aggregationModel, customHeaders: headers, cancellationToken: ct ).ConfigureAwait(false)); }, submitContext.CancellationToken ).ConfigureAwait(false); shouldContinue = await HandleSubmitResponse(submitContext, result, "Aggregation").ConfigureAwait(false); } catch (HttpOperationException ex) when(ex.Response?.StatusCode == System.Net.HttpStatusCode.Conflict) { // submitted item already exists! Nothing to do but continue with the submission pipeline LogVerbose(submitContext, nameof(Submit), $"Submission returned {ex.Response.StatusCode} : Aggregation already submitted."); } if (shouldContinue) { await InvokeNext(submitContext).ConfigureAwait(false); } }
/// <summary> /// /// </summary> /// <param name="submitContext"></param> /// <returns></returns> public override async Task Submit(SubmitContext submitContext) { var binarySubmitContext = submitContext as BinarySubmitContext; ValidateFields(binarySubmitContext); if (!CircuitProvider.IsCircuitClosed(out _)) { submitContext.SubmitResult.SubmitStatus = SubmitResult.Status.Deferred; return; } //Create the DirectBinarySubmissionInputModel needed for the SaS token call var binarySubmissionInputModel = new DirectBinarySubmissionInputModel( binarySubmitContext.ConnectorConfigId.ToString(), binarySubmitContext.ItemExternalId, binarySubmitContext.ExternalId, sourceLastModifiedDate: binarySubmitContext?.SourceLastModifiedDate, fileSize: binarySubmitContext.Stream.Length, fileName: binarySubmitContext.FileName, fileHash: binarySubmitContext.FileHash, mimeType: binarySubmitContext.MimeType ?? binarySubmitContext.SourceMetaData?.FirstOrDefault(metaInfo => metaInfo.Name == Fields.MimeType)?.Value, correlationId: binarySubmitContext.CorrelationId.ToString() ); // Get token and URL via Autorest-generated API call var apiClient = ApiClientFactory.CreateApiClient(submitContext.ApiClientFactorySettings); var retryPolicy = GetRetryPolicy(binarySubmitContext); var result = await retryPolicy.ExecuteAsync( async (ct) => { var authHelper = ApiClientFactory.CreateAuthenticationHelper(); var headers = await authHelper.GetHttpRequestHeaders(submitContext.AuthenticationHelperSettings).ConfigureAwait(false); return(await apiClient.ApiBinariesGetSASTokenPostWithHttpMessagesAsync( binarySubmissionInputModel: binarySubmissionInputModel, customHeaders: headers, cancellationToken: ct ).ConfigureAwait(false)); }, submitContext.CancellationToken ).ConfigureAwait(false); if (result.Response.StatusCode == HttpStatusCode.MethodNotAllowed) { // Direct binary submission is disabled, fall back to old submission method await base.Submit(submitContext).ConfigureAwait(false); return; } if (await HandleSubmitResponse(submitContext, result, "Binary").ConfigureAwait(false)) { var response = result.Body as DirectBinarySubmissionResponseModel; if (binarySubmitContext.Stream.CanSeek && binarySubmitContext.Stream.Length > response.MaxFileSize) { // We want to skip submission if the binary is too large. The CanSeek is to prevent NotSupportedException if // we attempt to get the length of an unseekable stream. // If we cannot seek the stream, we assume it's under the maxFileSize and allow submission. submitContext.SubmitResult.SubmitStatus = SubmitResult.Status.Skipped; return; } if (!binarySubmitContext.Stream.CanSeek) { //TODO: Log that submission is was allowed to proceed since size could not be determined. } // Retrieve reference to a blob. Use the DefaultBlobFactory if the BlobFactory on the pipeline element has not been set var blockBlob = BlobFactory != null?BlobFactory(response.Url) : DefaultBlobFactory(response.Url); // Set Blob ContentType blockBlob.Properties.ContentType = "application/octet-stream"; // If catch TooManyRequestsException, make it return a TooManyRequests Status try { await RetryProvider.ExecuteWithRetry( blockBlob.ServiceClient, //Upload to blob async() => { await blockBlob.UploadFromStreamAsync(binarySubmitContext.Stream, binarySubmitContext.CancellationToken).ConfigureAwait(false); }, GetType(), nameof(Submit)).ConfigureAwait(false); } catch (TooManyRequestsException ex) { submitContext.SubmitResult.SubmitStatus = SubmitResult.Status.TooManyRequests; submitContext.SubmitResult.WaitUntilTime = ex.WaitUntilTime; return; } if (!string.IsNullOrWhiteSpace(binarySubmitContext.FileName)) { blockBlob.Metadata[MetaDataKeys.ItemBinary_FileName] = EscapeBlobMetaDataValue(binarySubmitContext.FileName); blockBlob.Metadata[MetaDataKeys.ItemBinary_CorrelationId] = EscapeBlobMetaDataValue(binarySubmitContext.CorrelationId.ToString()); // If catch TooManyRequestsException, make it return a TooManyRequests Status try { await RetryProvider.ExecuteWithRetry( blockBlob.ServiceClient, async() => { await blockBlob.SetMetadataAsync(binarySubmitContext.CancellationToken).ConfigureAwait(false); }, GetType(), nameof(Submit)).ConfigureAwait(false); } catch (TooManyRequestsException ex) { submitContext.SubmitResult.SubmitStatus = SubmitResult.Status.TooManyRequests; submitContext.SubmitResult.WaitUntilTime = ex.WaitUntilTime; return; } } var notifyResult = await retryPolicy.ExecuteAsync( async (ct) => { //If the item corresponding to the submitted binary is not yet present, the platform will have to handle this. var authHelper = ApiClientFactory.CreateAuthenticationHelper(); var headers = await authHelper.GetHttpRequestHeaders(submitContext.AuthenticationHelperSettings).ConfigureAwait(false); return(await apiClient.ApiBinariesNotifyBinarySubmissionPostWithHttpMessagesAsync( binarySubmissionInputModel: binarySubmissionInputModel, customHeaders: headers, cancellationToken: ct ).ConfigureAwait(false)); }, submitContext.CancellationToken ).ConfigureAwait(false); if (notifyResult.Response.StatusCode != HttpStatusCode.OK) { var notificationStatusCode = "<No Status Code>"; if (result.Response != null) { notificationStatusCode = result.Response.StatusCode.ToString(); } // An issue with notification occurred, so we must throw throw new HttpOperationException(submitContext.LogPrefix() + $"Submission returned {notificationStatusCode} : Notification of binary submission failed."); } } else { return; } await InvokeNext(submitContext).ConfigureAwait(false); }