From eea9b0ae560ee1290347a8232b3b301380d26d4e Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Thu, 15 Dec 2022 12:38:34 +0100 Subject: [PATCH 1/9] feat: added a converter for GenericSearchConditionDto. --- ...enericSearchConditionSerializationTests.cs | 136 ++++++++++++++++++ .../GenericSearchConditionConverter.cs | 120 ++++++++++++++++ .../Data/GenericSearchConditionDto.cs | 30 +++- .../JsonGenericSearchConditionDtoConverter.cs | 103 +++++++++++++ .../Common/Client/ObjectSerializer.cs | 8 +- 5 files changed, 393 insertions(+), 4 deletions(-) create mode 100644 src/Api.Rest.Dtos.Tests/Data/GenericSearchConditionSerializationTests.cs create mode 100644 src/Api.Rest.Dtos/Converter/GenericSearchConditionConverter.cs create mode 100644 src/Api.Rest.Dtos/JsonConverters/JsonGenericSearchConditionDtoConverter.cs diff --git a/src/Api.Rest.Dtos.Tests/Data/GenericSearchConditionSerializationTests.cs b/src/Api.Rest.Dtos.Tests/Data/GenericSearchConditionSerializationTests.cs new file mode 100644 index 00000000..4357efed --- /dev/null +++ b/src/Api.Rest.Dtos.Tests/Data/GenericSearchConditionSerializationTests.cs @@ -0,0 +1,136 @@ +#region copyright + +/* * * * * * * * * * * * * * * * * * * * * * * * * */ +/* Carl Zeiss Industrielle Messtechnik GmbH */ +/* Softwaresystem PiWeb */ +/* (c) Carl Zeiss 2021 */ +/* * * * * * * * * * * * * * * * * * * * * * * * * */ + +#endregion + +namespace Zeiss.PiWeb.Api.Rest.Dtos.Tests.Data +{ + #region usings + + using System.Linq; + using Newtonsoft.Json; + using NUnit.Framework; + using Zeiss.PiWeb.Api.Rest.Dtos.Converter; + using Zeiss.PiWeb.Api.Rest.Dtos.Data; + + #endregion + + [TestFixture] + public class GenericSearchConditionSerializationTests + { + #region methods + + [Test] + public void GenericSearchConditionConverter_SerializesNotConditionProperly() + { + var original = (GenericSearchConditionDto)new GenericSearchNotDto + { + Condition = new GenericSearchFieldConditionDto + { + FieldName = "TestName", + Operation = OperationDto.GreaterThan, + Value = "TestValue" + } + }; + + + var serialized = JsonConvert.SerializeObject( original, Formatting.None, new GenericSearchConditionConverter() ); + var deserialized = JsonConvert.DeserializeObject( serialized, new GenericSearchConditionConverter() ); + var typedDeserialized = deserialized as GenericSearchNotDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.Condition, Is.TypeOf() ); + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + + [Test] + public void GenericSearchConditionConverter_SerializesAndConditionProperly() + { + var original = (GenericSearchConditionDto)new GenericSearchAndDto + { + Conditions = new[] + { + new GenericSearchFieldConditionDto + { + FieldName = "TestName", + Operation = OperationDto.GreaterThan, + Value = "TestValue" + }, + new GenericSearchFieldConditionDto + { + FieldName = "Test2Name", + Operation = OperationDto.GreaterThan, + Value = "Test2Value" + } + } + }; + + var serialized = JsonConvert.SerializeObject( original, Formatting.None, new GenericSearchConditionConverter() ); + var deserialized = JsonConvert.DeserializeObject( serialized, new GenericSearchConditionConverter() ); + var typedDeserialized = deserialized as GenericSearchAndDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.Conditions, Has.Exactly( 2 ).Items ); + Assert.That( typedDeserialized.Conditions.ElementAt( 0 ), Is.TypeOf() ); + Assert.That( typedDeserialized.Conditions.ElementAt( 1 ), Is.TypeOf() ); + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + + [Test] + public void GenericSearchConditionConverter_SerializesAttributeConditionProperly() + { + var original = new GenericSearchAttributeConditionDto + { + Attribute = 123, + Operation = OperationDto.GreaterThan, + Value = "TestValue" + }; + + var serialized = JsonConvert.SerializeObject( original, Formatting.None, new GenericSearchConditionConverter() ); + var deserialized = JsonConvert.DeserializeObject( serialized, new GenericSearchConditionConverter() ); + var typedDeserialized = deserialized as GenericSearchAttributeConditionDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.Attribute, Is.EqualTo( 123 ) ); + Assert.That( typedDeserialized.Operation, Is.EqualTo( OperationDto.GreaterThan ) ); + Assert.That( typedDeserialized.Value, Is.EqualTo( "TestValue" ) ); + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + + [Test] + public void GenericSearchConditionConverter_SerializesFieldConditionProperly() + { + var original = new GenericSearchFieldConditionDto + { + FieldName = "TestName", + Operation = OperationDto.GreaterThan, + Value = "TestValue" + }; + + var serialized = JsonConvert.SerializeObject( original, Formatting.None, new GenericSearchConditionConverter() ); + var deserialized = JsonConvert.DeserializeObject( serialized, new GenericSearchConditionConverter() ); + var typedDeserialized = deserialized as GenericSearchFieldConditionDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.FieldName, Is.EqualTo( "TestName" ) ); + Assert.That( typedDeserialized.Operation, Is.EqualTo( OperationDto.GreaterThan ) ); + Assert.That( typedDeserialized.Value, Is.EqualTo( "TestValue" ) ); + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Api.Rest.Dtos/Converter/GenericSearchConditionConverter.cs b/src/Api.Rest.Dtos/Converter/GenericSearchConditionConverter.cs new file mode 100644 index 00000000..0dd6c658 --- /dev/null +++ b/src/Api.Rest.Dtos/Converter/GenericSearchConditionConverter.cs @@ -0,0 +1,120 @@ +#region copyright + +/* * * * * * * * * * * * * * * * * * * * * * * * * */ +/* Carl Zeiss IMT (IZfM Dresden) */ +/* Softwaresystem PiWeb */ +/* (c) Carl Zeiss 2015 */ +/* * * * * * * * * * * * * * * * * * * * * * * * * */ + +#endregion + +namespace Zeiss.PiWeb.Api.Rest.Dtos.Converter +{ + #region usings + + using System; + using System.Collections.Generic; + using System.Linq; + using JetBrains.Annotations; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + using Zeiss.PiWeb.Api.Rest.Dtos.Data; + + #endregion + + /// + /// Specialized for -objects. + /// + public sealed class GenericSearchConditionConverter : JsonConverter + { + #region methods + + /// + public override bool CanConvert( Type objectType ) + { + return objectType == typeof( GenericSearchConditionDto ); + } + + /// + public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) + { + if( reader == null ) + throw new ArgumentNullException( nameof( reader ) ); + + if( reader.TokenType == JsonToken.Null ) + return null; + + var jToken = JToken.Load( reader ); + + if( jToken == null ) + throw new ArgumentNullException( nameof( jToken ) ); + + var target = existingValue ?? ReadJson( jToken, serializer ); + + if( target == null ) + throw new JsonSerializationException( "No object created." ); + + return target; + } + + [CanBeNull] + private static GenericSearchConditionDto ReadJson( JToken jToken, JsonSerializer serializer ) + { + using var jsonReader = new JTokenReader( jToken ); + + var fields = CollectFields( jToken ); + + if( fields.Contains( GenericSearchConditionDto.ConditionFieldName ) ) + return serializer.Deserialize( jsonReader ); + + if( fields.Contains( GenericSearchConditionDto.ConditionsFieldName ) ) + return serializer.Deserialize( jsonReader ); + + if( fields.Contains( GenericSearchConditionDto.FieldNameFieldName ) ) + return serializer.Deserialize( jsonReader ); + + if( fields.Contains( GenericSearchConditionDto.AttributeFieldName ) ) + return serializer.Deserialize( jsonReader ); + + return null; + } + + [NotNull] + private static HashSet CollectFields( JToken jToken ) + { + return new HashSet( jToken.Children().Select( i => i.Path ), StringComparer.OrdinalIgnoreCase ); + } + + /// + public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ) + { + switch( value ) + { + case null: + writer.WriteNull(); + break; + + case GenericSearchNotDto _: + serializer.Serialize( writer, value, typeof( GenericSearchNotDto ) ); + break; + + case GenericSearchAndDto _: + serializer.Serialize( writer, value, typeof( GenericSearchAndDto ) ); + break; + + case GenericSearchAttributeConditionDto _: + serializer.Serialize( writer, value, typeof( GenericSearchAttributeConditionDto ) ); + break; + + case GenericSearchFieldConditionDto _: + serializer.Serialize( writer, value, typeof( GenericSearchFieldConditionDto ) ); + break; + + default: + throw new JsonSerializationException( $"{nameof( value )} is invalid!" ); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Api.Rest.Dtos/Data/GenericSearchConditionDto.cs b/src/Api.Rest.Dtos/Data/GenericSearchConditionDto.cs index 659913c5..50e14340 100644 --- a/src/Api.Rest.Dtos/Data/GenericSearchConditionDto.cs +++ b/src/Api.Rest.Dtos/Data/GenericSearchConditionDto.cs @@ -10,7 +10,13 @@ namespace Zeiss.PiWeb.Api.Rest.Dtos.Data { + #region usings + using System.Collections.Generic; + using System.Text.Json.Serialization; + using Newtonsoft.Json; + + #endregion #region usings @@ -20,12 +26,23 @@ namespace Zeiss.PiWeb.Api.Rest.Dtos.Data /// Klasse zum Parsen einer Filterzeichenkette für Messdaten. /// public class GenericSearchConditionDto - { } + { + #region constants + + internal const string ConditionFieldName = "condition"; + internal const string ConditionsFieldName = "conditions"; + internal const string AttributeFieldName = "attribute"; + internal const string FieldNameFieldName = "fieldName"; + + #endregion + } public class GenericSearchNotDto : GenericSearchConditionDto { #region properties + [JsonProperty( ConditionFieldName )] + [JsonPropertyName( ConditionFieldName )] public GenericSearchConditionDto Condition { get; set; } #endregion @@ -53,6 +70,8 @@ public GenericSearchAndDto( IReadOnlyCollection filte #region properties + [JsonProperty( ConditionsFieldName )] + [JsonPropertyName( ConditionsFieldName )] public IReadOnlyCollection Conditions { get; set; } #endregion @@ -62,7 +81,12 @@ public class GenericSearchValueConditionDto : GenericSearchConditionDto { #region properties + [JsonProperty( "operation" )] + [JsonPropertyName( "operation" )] public OperationDto Operation { get; set; } + + [JsonProperty( "value" )] + [JsonPropertyName( "value" )] public string Value { get; set; } #endregion @@ -72,6 +96,8 @@ public class GenericSearchAttributeConditionDto : GenericSearchValueConditionDto { #region properties + [JsonProperty( AttributeFieldName )] + [JsonPropertyName( AttributeFieldName )] public ushort Attribute { get; set; } #endregion @@ -81,6 +107,8 @@ public class GenericSearchFieldConditionDto : GenericSearchValueConditionDto { #region properties + [JsonProperty( FieldNameFieldName )] + [JsonPropertyName( FieldNameFieldName )] public string FieldName { get; set; } #endregion diff --git a/src/Api.Rest.Dtos/JsonConverters/JsonGenericSearchConditionDtoConverter.cs b/src/Api.Rest.Dtos/JsonConverters/JsonGenericSearchConditionDtoConverter.cs new file mode 100644 index 00000000..1d1629ae --- /dev/null +++ b/src/Api.Rest.Dtos/JsonConverters/JsonGenericSearchConditionDtoConverter.cs @@ -0,0 +1,103 @@ +#region copyright + +/* * * * * * * * * * * * * * * * * * * * * * * * * */ +/* Carl Zeiss IMT (IZfM Dresden) */ +/* Softwaresystem PiWeb */ +/* (c) Carl Zeiss 2022 */ +/* * * * * * * * * * * * * * * * * * * * * * * * * */ + +#endregion + +namespace Zeiss.PiWeb.Api.Rest.Dtos.JsonConverters +{ + #region usings + + using System; + using System.Collections.Generic; + using System.Text.Json; + using System.Text.Json.Serialization; + using JetBrains.Annotations; + using Zeiss.PiWeb.Api.Rest.Dtos.Data; + + #endregion + + /// + /// Specialized for -objects. + /// + public sealed class JsonGenericSearchConditionDtoConverter : JsonConverter + { + #region methods + + /// + public override void Write( Utf8JsonWriter writer, GenericSearchConditionDto value, JsonSerializerOptions options ) + { + switch( value ) + { + case null: + writer.WriteNullValue(); + break; + + case GenericSearchNotDto condition: + JsonSerializer.Serialize( writer, condition, options ); + break; + + case GenericSearchAndDto condition: + JsonSerializer.Serialize( writer, condition, options ); + break; + + case GenericSearchAttributeConditionDto condition: + JsonSerializer.Serialize( writer, condition, options ); + break; + + case GenericSearchFieldConditionDto condition: + JsonSerializer.Serialize( writer, condition, options ); + break; + + default: + throw new NotImplementedException( $"{value.GetType().Name}" ); + } + } + + /// + [CanBeNull] + public override GenericSearchConditionDto Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options ) + { + var propertyNames = CollectPropertyNames( reader ); + + if( propertyNames.Contains( GenericSearchConditionDto.ConditionFieldName ) ) + return JsonSerializer.Deserialize( ref reader, options ); + + if( propertyNames.Contains( GenericSearchConditionDto.ConditionsFieldName ) ) + return JsonSerializer.Deserialize( ref reader, options ); + + if( propertyNames.Contains( GenericSearchConditionDto.AttributeFieldName ) ) + return JsonSerializer.Deserialize( ref reader, options ); + + if( propertyNames.Contains( GenericSearchConditionDto.FieldNameFieldName ) ) + return JsonSerializer.Deserialize( ref reader, options ); + + throw new NotImplementedException( "Encountered unknown search condition type" ); + } + + + [NotNull] + private static HashSet CollectPropertyNames( Utf8JsonReader reader ) + { + var propertyNames = new HashSet( StringComparer.OrdinalIgnoreCase ); + + var startDepth = reader.CurrentDepth; + + while( reader.Read() && ( reader.TokenType != JsonTokenType.EndObject || reader.CurrentDepth != startDepth ) ) + { + if( reader.TokenType == JsonTokenType.PropertyName && reader.CurrentDepth == startDepth + 1 ) + propertyNames.Add( reader.GetString() ); + else + reader.TrySkip(); + } + + return propertyNames; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Api.Rest/Common/Client/ObjectSerializer.cs b/src/Api.Rest/Common/Client/ObjectSerializer.cs index 45a823a4..eb04f1bf 100644 --- a/src/Api.Rest/Common/Client/ObjectSerializer.cs +++ b/src/Api.Rest/Common/Client/ObjectSerializer.cs @@ -62,7 +62,8 @@ private static Newtonsoft.Json.JsonSerializer CreateJsonSerializer() Converters = { new Newtonsoft.Json.Converters.VersionConverter(), - new InspectionPlanDtoBaseConverter() + new InspectionPlanDtoBaseConverter(), + new GenericSearchConditionConverter() } }; } @@ -121,7 +122,7 @@ Task IObjectSerializer.DeserializeAsync( Stream stream, CancellationToken IAsyncEnumerable IObjectSerializer.DeserializeAsyncEnumerable( Stream stream, CancellationToken cancellationToken ) { if( stream == null ) throw new ArgumentNullException( nameof( stream ) ); - + async IAsyncEnumerable DeserializeAsyncEnumerable() { var items = Deserialize>( stream ); @@ -154,7 +155,8 @@ private sealed class SystemTextJsonSerializer : IObjectSerializer Converters = { - new JsonInspectionPlanDtoBaseConverter() + new JsonInspectionPlanDtoBaseConverter(), + new JsonGenericSearchConditionDtoConverter() } }; From fd862080eefd5ee302f2358f79c6a9e6f086a2b0 Mon Sep 17 00:00:00 2001 From: Alexander Thiele Date: Wed, 21 Dec 2022 10:01:00 +0100 Subject: [PATCH 2/9] Add tests for request splitting in GetMeasurements --- .../Data/DataServiceRestClientTest.cs | 80 +++++++++++++++++++ src/Api.Rest.Tests/WebServer.cs | 35 +++++--- 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/Api.Rest.Tests/HttpClient/Data/DataServiceRestClientTest.cs b/src/Api.Rest.Tests/HttpClient/Data/DataServiceRestClientTest.cs index b863efcc..df1cb2a8 100644 --- a/src/Api.Rest.Tests/HttpClient/Data/DataServiceRestClientTest.cs +++ b/src/Api.Rest.Tests/HttpClient/Data/DataServiceRestClientTest.cs @@ -13,6 +13,8 @@ namespace Zeiss.PiWeb.Api.Rest.Tests.HttpClient.Data #region usings using System; + using System.Collections.Generic; + using System.Linq; using System.Threading.Tasks; using FluentAssertions; using Newtonsoft.Json; @@ -110,6 +112,84 @@ public async Task RequestSplit_LimitResultSpecified_LimitCorrect() result.Count.Should().Be( 5 ); } + [Test] + public async Task GetMeasurementValues_UriTooLong_RequestSplit() + { + using var server = WebServer.StartNew( Port ); + //maxUriLength of 200 leads to request splitting + using var client = new DataServiceRestClient( Uri, 200 ); + + server.RegisterDefaultResponse( "[]" ); + + var uuids = new List(); + + for( var i = 0; i < 20; i++ ) + uuids.Add( Guid.NewGuid() ); + + await client.GetMeasurementValues( filter: new MeasurementValueFilterAttributesDto { MeasurementUuids = uuids, CharacteristicsUuidList = uuids } ); + + server.ReceivedRequests.Count().Should().BeGreaterThan( 5 ); + } + + [Test] + public async Task GetMeasurementValues_UriTooLong_UriLengthCorrect() + { + using var server = WebServer.StartNew( Port ); + //maxUriLength of 200 leads to request splitting + using var client = new DataServiceRestClient( Uri, 200 ); + + server.RegisterDefaultResponse( "[]" ); + + var uuids = new List(); + + for( var i = 0; i < 20; i++ ) + uuids.Add( Guid.NewGuid() ); + + await client.GetMeasurementValues( filter: new MeasurementValueFilterAttributesDto { MeasurementUuids = uuids, CharacteristicsUuidList = uuids } ); + + foreach( var request in server.ReceivedRequests ) + request.Length.Should().BeLessOrEqualTo( 200 ); + } + + [Test] + public async Task GetMeasurements_UriTooLong_RequestSplit() + { + using var server = WebServer.StartNew( Port ); + //maxUriLength of 200 leads to request splitting + using var client = new DataServiceRestClient( Uri, 200 ); + + server.RegisterDefaultResponse( "[]" ); + + var uuids = new List(); + + for( var i = 0; i < 20; i++ ) + uuids.Add( Guid.NewGuid() ); + + await client.GetMeasurements( filter: new MeasurementFilterAttributesDto { MeasurementUuids = uuids } ); + + server.ReceivedRequests.Count().Should().BeGreaterThan( 5 ); + } + + [Test] + public async Task GetMeasurements_UriTooLong_UriLengthCorrect() + { + using var server = WebServer.StartNew( Port ); + //maxUriLength of 200 leads to request splitting + using var client = new DataServiceRestClient( Uri, 200 ); + + server.RegisterDefaultResponse( "[]" ); + + var uuids = new List(); + + for( var i = 0; i < 20; i++ ) + uuids.Add( Guid.NewGuid() ); + + await client.GetMeasurements( filter: new MeasurementFilterAttributesDto { MeasurementUuids = uuids } ); + + foreach( var request in server.ReceivedRequests ) + request.Length.Should().BeLessOrEqualTo( 200 ); + } + #endregion } } \ No newline at end of file diff --git a/src/Api.Rest.Tests/WebServer.cs b/src/Api.Rest.Tests/WebServer.cs index 4a278a37..0cf937b9 100644 --- a/src/Api.Rest.Tests/WebServer.cs +++ b/src/Api.Rest.Tests/WebServer.cs @@ -24,8 +24,10 @@ internal sealed class WebServer : IDisposable { #region members - private readonly HttpListener _Listener = new HttpListener(); + private readonly HttpListener _Listener = new(); private readonly IDictionary> _Responses = new Dictionary>(); + private readonly List _ReceivedRequests; + private string _DefaultResponse; #endregion @@ -35,10 +37,17 @@ private WebServer( int port ) { var prefix = $"http://+:{port}/"; _Listener.Prefixes.Add( prefix ); + _ReceivedRequests = new List(); } #endregion + #region properties + + public IEnumerable ReceivedRequests => _ReceivedRequests; + + #endregion + #region methods public static WebServer StartNew( int port ) @@ -58,18 +67,21 @@ private void Start() { while( _Listener.IsListening ) { - var ctx = await _Listener.GetContextAsync(); + var context = await _Listener.GetContextAsync(); + + if( context.Request.Url is not null ) + _ReceivedRequests.Add( context.Request.Url.ToString() ); try { - var rstr = CreateResponse( ctx.Request ); - var buf = Encoding.UTF8.GetBytes( rstr ); - ctx.Response.ContentLength64 = buf.Length; - await ctx.Response.OutputStream.WriteAsync( buf, 0, buf.Length ); + var response = CreateResponse( context.Request ); + var buf = Encoding.UTF8.GetBytes( response ); + context.Response.ContentLength64 = buf.Length; + await context.Response.OutputStream.WriteAsync( buf, 0, buf.Length ); } finally { - ctx.Response.OutputStream.Close(); + context.Response.OutputStream.Close(); } } } ); @@ -85,12 +97,17 @@ public void RegisterResponse( string uri, string response ) RegisterResponse( uri, () => response ); } + public void RegisterDefaultResponse( string response ) + { + _DefaultResponse = response; + } + private string CreateResponse( HttpListenerRequest request ) { - if( _Responses.TryGetValue( request.RawUrl, out var response ) ) + if( request.RawUrl != null && _Responses.TryGetValue( request.RawUrl, out var response ) ) return response(); - return string.Empty; + return _DefaultResponse ?? string.Empty; } #endregion From 359cb2a761005d5a97e3b30d92c3dc0b5da28ece Mon Sep 17 00:00:00 2001 From: Michael Dietrich Date: Thu, 2 Feb 2023 11:34:33 +0100 Subject: [PATCH 3/9] [PIWEB-14953] feat: Add option to ignore local OAuth token cache --- src/Api.Rest/Common/Utilities/OAuthHelper.cs | 190 ++++++++++++------- 1 file changed, 119 insertions(+), 71 deletions(-) diff --git a/src/Api.Rest/Common/Utilities/OAuthHelper.cs b/src/Api.Rest/Common/Utilities/OAuthHelper.cs index 5f795872..056ed141 100644 --- a/src/Api.Rest/Common/Utilities/OAuthHelper.cs +++ b/src/Api.Rest/Common/Utilities/OAuthHelper.cs @@ -29,6 +29,9 @@ namespace Zeiss.PiWeb.Api.Rest.Common.Utilities #endregion + /// + /// Provides a number of useful methods for handling OAuth authentication. + /// public static class OAuthHelper { #region constants @@ -148,14 +151,14 @@ private static string GetInstanceUrl( [NotNull] string databaseUrl ) private static OAuthTokenCredential TryGetCurrentOAuthToken( string instanceUrl, ref string refreshToken ) { // if access token is still valid (5min margin to allow for clock skew), just return it from the cache - if( AccessTokenCache.TryGetCredential( instanceUrl, out var result ) ) - { - if( string.IsNullOrEmpty( refreshToken ) ) - refreshToken = result.RefreshToken; + if( !AccessTokenCache.TryGetCredential( instanceUrl, out var result ) ) + return null; - if( result.AccessTokenExpiration.AddMinutes( -5 ) > DateTime.UtcNow ) - return result; - } + if( string.IsNullOrEmpty( refreshToken ) ) + refreshToken = result.RefreshToken; + + if( result.AccessTokenExpiration.AddMinutes( -5 ) > DateTime.UtcNow ) + return result; return null; } @@ -185,28 +188,32 @@ private static async Task TryGetOAuthTokenFromRefreshToken tokenResponse.RefreshToken ); } - private static async Task TryGetOAuthTokenFromAuthorizeResponseAsync( TokenClient tokenClient, CryptoNumbers cryptoNumbers, AuthorizeResponse response ) + private static async Task TryGetOAuthTokenFromAuthorizeResponseAsync( + TokenClient tokenClient, + CryptoNumbers cryptoNumbers, + AuthorizeResponse response ) { if( response == null ) return null; - // claims des IdentityToken decodieren + // decode the claim's IdentityToken var claims = DecodeSecurityToken( response.IdentityToken ).Claims.ToArray(); - // die folgenden validierungen sind notwendig, um diversen CSRF / man in the middle / etc. Angriffsszenarien zu begegnen - // state validieren + // the following validation is necessary to protect against several kinds of CSRF / man-in-the-middle and other attack scenarios + // state validation if( !string.Equals( cryptoNumbers.State, response.State, StringComparison.Ordinal ) ) throw new InvalidOperationException( "invalid state value in openid service response." ); - // nonce validieren + // nonce validation if( !ValidateNonce( cryptoNumbers.Nonce, claims ) ) throw new InvalidOperationException( "invalid nonce value in identity token." ); - // c_hash validieren + // c_hash validation if( !ValidateCodeHash( response.Code, claims ) ) throw new InvalidOperationException( "invalid c_hash value in identity token." ); - // code eintauschen gegen access token und refresh token, dabei den code verifier mitschicken, um man-in-the-middle Angriff auf authorization code zu begegnen (PKCE) + // exchange the code for the access and refresh token, also send the code verifier, + // to handle man-in-the-middle attacks against the authorization code (PKCE) var tokenResponse = await tokenClient.RequestAuthorizationCodeTokenAsync( code: response.Code, redirectUri: RedirectUri, @@ -255,92 +262,127 @@ private static TokenClient CreateTokenClient( string tokenEndpoint ) } ); } - public static async Task GetAuthenticationInformationForDatabaseUrlAsync( string databaseUrl, string refreshToken = null, Func> requestCallbackAsync = null ) + /// + /// Retrieves authentication information for a given database URL. + /// + /// Database URL to retrieve authentication information for. + /// Optional refresh token that is used to renew the authentication information. + /// Optional callback to request the user to interactively authenticate. + /// Defines whether locally cached token information are neither used nor updated. + /// A new instance, or null, if no token could be retrieved. + public static async Task GetAuthenticationInformationForDatabaseUrlAsync( + string databaseUrl, + string refreshToken = null, + Func> requestCallbackAsync = null, + bool bypassLocalCache = false ) { var instanceUrl = GetInstanceUrl( databaseUrl ); - var result = TryGetCurrentOAuthToken( instanceUrl, ref refreshToken ); - if( result != null ) - return result; + if( !bypassLocalCache ) + { + var cachedToken = TryGetCurrentOAuthToken( instanceUrl, ref refreshToken ); + if( cachedToken != null ) + return cachedToken; + } var discoveryInfo = await GetDiscoveryInfoAsync( instanceUrl ).ConfigureAwait( false ); if( discoveryInfo.IsError ) return null; var tokenClient = CreateTokenClient( discoveryInfo.TokenEndpoint ); - result = await TryGetOAuthTokenFromRefreshTokenAsync( tokenClient, discoveryInfo.UserInfoEndpoint, refreshToken ).ConfigureAwait( false ); + var result = await TryGetOAuthTokenFromRefreshTokenAsync( tokenClient, discoveryInfo.UserInfoEndpoint, refreshToken ).ConfigureAwait( false ); if( result != null ) { - AccessTokenCache.Store( instanceUrl, result ); + if( !bypassLocalCache ) + AccessTokenCache.Store( instanceUrl, result ); + return result; } // no refresh token or refresh token expired, do the full auth cycle eventually involving a password prompt - if( requestCallbackAsync != null ) - { - var cryptoNumbers = new CryptoNumbers(); - var startUrl = CreateOAuthStartUrl( discoveryInfo.AuthorizeEndpoint, cryptoNumbers ); - - var request = new OAuthRequest( startUrl, RedirectUri ); - var response = ( await requestCallbackAsync( request ).ConfigureAwait( false ) )?.ToAuthorizeResponse(); - if( response != null ) - { - result = await TryGetOAuthTokenFromAuthorizeResponseAsync( tokenClient, cryptoNumbers, response ).ConfigureAwait( false ); - if( result != null ) - { - AccessTokenCache.Store( instanceUrl, result ); - return result; - } - } - } + if( requestCallbackAsync == null ) + return null; - return null; + var cryptoNumbers = new CryptoNumbers(); + var startUrl = CreateOAuthStartUrl( discoveryInfo.AuthorizeEndpoint, cryptoNumbers ); + + var request = new OAuthRequest( startUrl, RedirectUri ); + var response = ( await requestCallbackAsync( request ).ConfigureAwait( false ) )?.ToAuthorizeResponse(); + if( response == null ) + return null; + + result = await TryGetOAuthTokenFromAuthorizeResponseAsync( tokenClient, cryptoNumbers, response ).ConfigureAwait( false ); + if( result == null ) + return null; + + if( !bypassLocalCache ) + AccessTokenCache.Store( instanceUrl, result ); + + return result; } - public static OAuthTokenCredential GetAuthenticationInformationForDatabaseUrl( string databaseUrl, string refreshToken = null, Func requestCallback = null ) + /// + /// Retrieves authentication information for a given database URL. + /// + /// Database URL to retrieve authentication information for. + /// Optional refresh token that is used to renew the authentication information. + /// Optional callback to request the user to interactively authenticate. + /// Defines whether locally cached token information are neither used nor updated. + /// A new instance, or null, if no token could be retrieved. + public static OAuthTokenCredential GetAuthenticationInformationForDatabaseUrl( + string databaseUrl, + string refreshToken = null, + Func requestCallback = null, + bool bypassLocalCache = false ) { var instanceUrl = GetInstanceUrl( databaseUrl ); - var result = TryGetCurrentOAuthToken( instanceUrl, ref refreshToken ); - if( result != null ) - return result; + if( !bypassLocalCache ) + { + var cachedToken = TryGetCurrentOAuthToken( instanceUrl, ref refreshToken ); + if( cachedToken != null ) + return cachedToken; + } var discoveryInfo = GetDiscoveryInfoAsync( instanceUrl ).GetAwaiter().GetResult(); if( discoveryInfo.IsError ) return null; var tokenClient = CreateTokenClient( discoveryInfo.TokenEndpoint ); - result = TryGetOAuthTokenFromRefreshTokenAsync( tokenClient, discoveryInfo.UserInfoEndpoint, refreshToken ).GetAwaiter().GetResult(); + var result = TryGetOAuthTokenFromRefreshTokenAsync( tokenClient, discoveryInfo.UserInfoEndpoint, refreshToken ).GetAwaiter().GetResult(); if( result != null ) { - AccessTokenCache.Store( instanceUrl, result ); + if( !bypassLocalCache ) + AccessTokenCache.Store( instanceUrl, result ); + return result; } // no refresh token or refresh token expired, do the full auth cycle eventually involving a password prompt - if( requestCallback != null ) - { - var cryptoNumbers = new CryptoNumbers(); - var startUrl = CreateOAuthStartUrl( discoveryInfo.AuthorizeEndpoint, cryptoNumbers ); - - var request = new OAuthRequest( startUrl, RedirectUri ); - var response = requestCallback( request )?.ToAuthorizeResponse(); - if( response != null ) - { - result = TryGetOAuthTokenFromAuthorizeResponseAsync( tokenClient, cryptoNumbers, response ).GetAwaiter().GetResult(); - if( result != null ) - { - AccessTokenCache.Store( instanceUrl, result ); - return result; - } - } - } + if( requestCallback == null ) + return null; - return null; + var cryptoNumbers = new CryptoNumbers(); + var startUrl = CreateOAuthStartUrl( discoveryInfo.AuthorizeEndpoint, cryptoNumbers ); + + var request = new OAuthRequest( startUrl, RedirectUri ); + var response = requestCallback( request )?.ToAuthorizeResponse(); + + if( response == null ) + return null; + + result = TryGetOAuthTokenFromAuthorizeResponseAsync( tokenClient, cryptoNumbers, response ).GetAwaiter().GetResult(); + if( result == null ) + return null; + + if( !bypassLocalCache ) + AccessTokenCache.Store( instanceUrl, result ); + + return result; } /// - /// Request target server for authentification settings. + /// Request target server for authentication settings. /// private static async Task GetDiscoveryInfoAsync( string instanceUrl ) { @@ -378,18 +420,21 @@ private static bool ValidateCodeHash( string authorizationCode, IEnumerable c.Type == JwtClaimTypes.AuthorizationCodeHash ); - using( var sha = SHA256.Create() ) - { - var codeHash = sha.ComputeHash( Encoding.ASCII.GetBytes( authorizationCode ) ); - var leftBytes = new byte[ 16 ]; - Array.Copy( codeHash, leftBytes, 16 ); + using var sha = SHA256.Create(); - var codeHashB64 = Base64Url.Encode( leftBytes ); + var codeHash = sha.ComputeHash( Encoding.ASCII.GetBytes( authorizationCode ) ); + var leftBytes = new byte[ 16 ]; + Array.Copy( codeHash, leftBytes, 16 ); - return string.Equals( cHash.Value, codeHashB64, StringComparison.Ordinal ); - } + var codeHashB64 = Base64Url.Encode( leftBytes ); + + return string.Equals( cHash.Value, codeHashB64, StringComparison.Ordinal ); } + /// + /// Clears locally cached authentication information for a given database URL. + /// + /// Database URL to clear authentication information for. public static void ClearAuthenticationInformationForDatabaseUrl( string databaseUrl ) { var instanceUrl = GetInstanceUrl( databaseUrl ); @@ -399,6 +444,9 @@ public static void ClearAuthenticationInformationForDatabaseUrl( string database // https://identityserver.github.io/Documentation/docsv2/endpoints/endSession.html } + /// + /// Clears all locally cached authentication information. + /// public static void ClearAllAuthenticationInformation() { AccessTokenCache.Clear(); From 3188d647ef2352a5ceb9f36aa4763c51c79f997b Mon Sep 17 00:00:00 2001 From: Stefan Kovacevic Date: Sun, 5 Feb 2023 20:25:21 +0100 Subject: [PATCH 4/9] [PIWEB-14972] Case sensitive Measurement and MeasurementValues serach --- .../AbstractMeasurementFilterAttributesDto.cs | 7 ++ .../Data/MeasurementFilterAttributesDto.cs | 99 ++++++++++--------- .../MeasurementValueFilterAttributesDto.cs | 13 ++- 3 files changed, 72 insertions(+), 47 deletions(-) diff --git a/src/Api.Rest.Dtos/Data/AbstractMeasurementFilterAttributesDto.cs b/src/Api.Rest.Dtos/Data/AbstractMeasurementFilterAttributesDto.cs index 8bba2bfb..9634e3e2 100644 --- a/src/Api.Rest.Dtos/Data/AbstractMeasurementFilterAttributesDto.cs +++ b/src/Api.Rest.Dtos/Data/AbstractMeasurementFilterAttributesDto.cs @@ -34,6 +34,7 @@ public abstract class AbstractMeasurementFilterAttributesDto protected const string OrderByParamName = "order"; protected const string SearchConditionParamName = "searchCondition"; + protected const string CaseSensitiveParamName = "caseSensitive"; protected const string AggregationParamName = "aggregation"; protected const string FromModificationDateParamName = "fromModificationDate"; protected const string ToModificationDateParamName = "toModificationDate"; @@ -91,6 +92,12 @@ protected AbstractMeasurementFilterAttributesDto() /// public GenericSearchConditionDto SearchCondition { get; set; } + /// + /// Gets or sets a flag if the search criteria should be compared case sensitive, case insensitive, + /// or by database default (if left to null) + /// + public bool? CaseSensitive { get; set; } + /// /// Gets or sets the list of measurement uuids that should be returned. /// diff --git a/src/Api.Rest.Dtos/Data/MeasurementFilterAttributesDto.cs b/src/Api.Rest.Dtos/Data/MeasurementFilterAttributesDto.cs index ea1e898a..67bb4d59 100644 --- a/src/Api.Rest.Dtos/Data/MeasurementFilterAttributesDto.cs +++ b/src/Api.Rest.Dtos/Data/MeasurementFilterAttributesDto.cs @@ -1,43 +1,43 @@ #region copyright - -/* * * * * * * * * * * * * * * * * * * * * * * * * */ -/* Carl Zeiss IMT (IZfM Dresden) */ -/* Softwaresystem PiWeb */ -/* (c) Carl Zeiss 2015 */ -/* * * * * * * * * * * * * * * * * * * * * * * * * */ - + +/* * * * * * * * * * * * * * * * * * * * * * * * * */ +/* Carl Zeiss IMT (IZfM Dresden) */ +/* Softwaresystem PiWeb */ +/* (c) Carl Zeiss 2015 */ +/* * * * * * * * * * * * * * * * * * * * * * * * * */ + #endregion - + namespace Zeiss.PiWeb.Api.Rest.Dtos.Data -{ +{ #region usings - + using System; using System.Collections.Generic; using System.Globalization; using System.Linq; - using System.Xml; - + using System.Xml; + #endregion - + /// /// This class contains a measurement search criteria for a measurement search. /// public class MeasurementFilterAttributesDto : AbstractMeasurementFilterAttributesDto - { + { #region constants - + private const string RequestedMeasurementAttributesParamName = "requestedmeasurementattributes"; private const string StatisticsParamName = "statistics"; private const string MergeAttributesParamName = "mergeattributes"; private const string MergeConditionParamName = "mergecondition"; - private const string MergeMasterPartParamName = "mergemasterpart"; - + private const string MergeMasterPartParamName = "mergemasterpart"; + #endregion - + #region constructors - + /// /// Initializes a new instance of the class. /// @@ -49,45 +49,45 @@ public MeasurementFilterAttributesDto() MergeMasterPart = null; Statistics = MeasurementStatisticsDto.None; - } - + } + #endregion - + #region properties - + /// /// Gets or sets the selector for the measurement attributes. /// - public AttributeSelector RequestedMeasurementAttributes { get; set; } - + public AttributeSelector RequestedMeasurementAttributes { get; set; } + /// /// Specifies if statistical information (: number characteristics OOT, OOT, etc.) should be returned. /// - public MeasurementStatisticsDto Statistics { get; set; } - + public MeasurementStatisticsDto Statistics { get; set; } + /// /// Specifies the list of primary measurement keys to be used for joining measurements accross multiple parts on the server side. /// - public IReadOnlyList MergeAttributes { get; set; } - + public IReadOnlyList MergeAttributes { get; set; } + /// /// Specifies the condition that must be adhered to /// when merging measurements accross multiple parts using a primary key. /// Default value is MeasurementMergeCondition.MeasurementsInAllParts. /// - public MeasurementMergeConditionDto MergeCondition { get; set; } - + public MeasurementMergeConditionDto MergeCondition { get; set; } + /// /// Specifies the part to be used as master part /// when merging measurements accross multiple parts using a primary key. /// Default value is null. /// - public Guid? MergeMasterPart { get; set; } - + public Guid? MergeMasterPart { get; set; } + #endregion - + #region methods - + /// /// Parses the filter and returns a object that represents the filter values. /// If the parse operation was not successful, an will be thrown. @@ -101,6 +101,7 @@ public static MeasurementFilterAttributesDto Parse( string order, string requestedMeasurementAttributes, string searchCondition, + string caseSensitive, string statistics, string aggregation, string fromModificationDate, @@ -108,7 +109,7 @@ public static MeasurementFilterAttributesDto Parse( string mergeAttributes, string mergeCondition, string mergeMasterPart, - string limitResultPerPart = "-1") + string limitResultPerPart = "-1" ) { var items = new[] { @@ -120,6 +121,7 @@ public static MeasurementFilterAttributesDto Parse( ValueTuple.Create( OrderByParamName, order ), ValueTuple.Create( RequestedMeasurementAttributesParamName, requestedMeasurementAttributes ), ValueTuple.Create( SearchConditionParamName, searchCondition ), + ValueTuple.Create( CaseSensitiveParamName, caseSensitive ), ValueTuple.Create( StatisticsParamName, statistics ), ValueTuple.Create( AggregationParamName, aggregation ), ValueTuple.Create( FromModificationDateParamName, fromModificationDate ), @@ -163,6 +165,9 @@ public static MeasurementFilterAttributesDto Parse( case SearchConditionParamName: result.SearchCondition = SearchConditionParser.Parse( value ); break; + case CaseSensitiveParamName: + result.CaseSensitive = bool.Parse( value ); + break; case StatisticsParamName: result.Statistics = (MeasurementStatisticsDto)Enum.Parse( typeof( MeasurementStatisticsDto ), value ); break; @@ -188,13 +193,13 @@ public static MeasurementFilterAttributesDto Parse( } catch( Exception ex ) { - throw new InvalidOperationException( $"Invalid filter value '{value}' for parameter '{key}'. The can be specified via url parameter in the form of 'key=value'. The following keys are valid: {"partUuids: [list of part uuids]\r\n" + "deep: [True|False]\r\n" + "limitResult: [short]\r\n" + "measurementUuids: [list of measurement uuids]\r\n" + "measurementAttributes: [attribute keys csv|Empty for all attributes]\r\n" + "orderBy:[ushort asc|desc, ushort asc|desc, ...]\r\n" + "searchCondition:[search filter string]\r\n" + "aggregation:[Measurements|AggregationMeasurements|Default|All]\r\n" + "statistics:[None|Simple|Detailed]\r\n" + "mergeAttributes:[list of measurement attributes]\r\n" + "mergeCondition: [None|MeasurementsInAtLeastTwoParts|MeasurementsInAllParts]\r\n" + "mergeMasterPart: [part uuid]\r\n" + "fromModificationDate:[Date]\r\n" + "toModificationDate:[Date]"}", ex ); + throw new InvalidOperationException( $"Invalid filter value '{value}' for parameter '{key}'. The can be specified via url parameter in the form of 'key=value'. The following keys are valid: {"partUuids: [list of part uuids]\r\n" + "deep = [True|False]\r\n" + "limitResult: [short]\r\n" + "measurementUuids: [list of measurement uuids]\r\n" + "measurementAttributes: [attribute keys csv|Empty for all attributes]\r\n" + "orderBy:[ushort asc|desc, ushort asc|desc, ...]\r\n" + "searchCondition:[search filter string]\r\n" + "caseSensitive: [True|False]\r\n" + "aggregation:[Measurements|AggregationMeasurements|Default|All]\r\n" + "statistics:[None|Simple|Detailed]\r\n" + "mergeAttributes:[list of measurement attributes]\r\n" + "mergeCondition: [None|MeasurementsInAtLeastTwoParts|MeasurementsInAllParts]\r\n" + "mergeMasterPart: [part uuid]\r\n" + "fromModificationDate:[Date]\r\n" + "toModificationDate:[Date]"}", ex ); } } return result; - } - + } + /// /// Converts this filter object into an equivalent object. /// @@ -220,8 +225,8 @@ public MeasurementValueFilterAttributesDto ToMeasurementValueFilterAttributes() MergeCondition = MergeCondition, MergeMasterPart = MergeMasterPart }; - } - + } + /// /// Creates a shallow clone of this filter. /// @@ -236,6 +241,7 @@ public MeasurementFilterAttributesDto Clone() OrderBy = OrderBy, RequestedMeasurementAttributes = RequestedMeasurementAttributes, SearchCondition = SearchCondition, + CaseSensitive = CaseSensitive, MeasurementUuids = MeasurementUuids, AggregationMeasurements = AggregationMeasurements, FromModificationDate = FromModificationDate, @@ -244,8 +250,8 @@ public MeasurementFilterAttributesDto Clone() MergeCondition = MergeCondition, MergeMasterPart = MergeMasterPart }; - } - + } + /// public override IReadOnlyCollection ToParameterDefinition() { @@ -275,6 +281,9 @@ public override IReadOnlyCollection ToParameterDefinition() if( SearchCondition != null ) result.Add( ParameterDefinition.Create( SearchConditionParamName, SearchConditionParser.GenericConditionToString( SearchCondition ) ) ); + if( CaseSensitive != null && CaseSensitive.Value ) + result.Add( ParameterDefinition.Create( CaseSensitiveParamName, CaseSensitive.ToString() ) ); + if( Statistics != MeasurementStatisticsDto.None ) result.Add( ParameterDefinition.Create( StatisticsParamName, Statistics.ToString() ) ); @@ -297,8 +306,8 @@ public override IReadOnlyCollection ToParameterDefinition() result.Add( ParameterDefinition.Create( MergeMasterPartParamName, MergeMasterPart.ToString() ) ); return result; - } - + } + #endregion } } \ No newline at end of file diff --git a/src/Api.Rest.Dtos/Data/MeasurementValueFilterAttributesDto.cs b/src/Api.Rest.Dtos/Data/MeasurementValueFilterAttributesDto.cs index 8ba78f9c..06b4826e 100644 --- a/src/Api.Rest.Dtos/Data/MeasurementValueFilterAttributesDto.cs +++ b/src/Api.Rest.Dtos/Data/MeasurementValueFilterAttributesDto.cs @@ -110,6 +110,7 @@ public static MeasurementValueFilterAttributesDto Parse( string requestedMeasurementAttributes, string requestedValueAttributes, string searchCondition, + string caseSensitive, string aggregation, string fromModificationDate, string toModificationDate, @@ -118,7 +119,7 @@ public static MeasurementValueFilterAttributesDto Parse( string mergeMasterPart, string limitResultPerPart = "-1" ) { - var items = new [] + var items = new[] { ValueTuple.Create( PartUuidsParamName, partUuids ), ValueTuple.Create( MeasurementUuidsParamName, measurementUuids ), @@ -130,6 +131,7 @@ public static MeasurementValueFilterAttributesDto Parse( ValueTuple.Create( RequestedValueAttributesParamName, requestedValueAttributes ), ValueTuple.Create( RequestedMeasurementAttributesParamName, requestedMeasurementAttributes ), ValueTuple.Create( SearchConditionParamName, searchCondition ), + ValueTuple.Create( CaseSensitiveParamName, caseSensitive ), ValueTuple.Create( AggregationParamName, aggregation ), ValueTuple.Create( FromModificationDateParamName, fromModificationDate ), ValueTuple.Create( ToModificationDateParamName, toModificationDate ), @@ -178,6 +180,9 @@ public static MeasurementValueFilterAttributesDto Parse( case SearchConditionParamName: result.SearchCondition = SearchConditionParser.Parse( value ); break; + case CaseSensitiveParamName: + result.CaseSensitive = bool.Parse( value ); + break; case AggregationParamName: result.AggregationMeasurements = (AggregationMeasurementSelectionDto)Enum.Parse( typeof( AggregationMeasurementSelectionDto ), value ); break; @@ -200,7 +205,7 @@ public static MeasurementValueFilterAttributesDto Parse( } catch( Exception ex ) { - throw new InvalidOperationException( $"Invalid filter value '{value}' for parameter '{key}'. The can be specified via url parameter in the form of 'key=value'. The following keys are valid: {"partUuids = [list of part uuids]\r\n" + "deep = [True|False]\r\n" + "limitResult = [short]\r\n" + "measurementUuids = [list of measurement uuids]\r\n" + "characteristicUuids = [list of characteristic uuids]\r\n" + "valueAttributes = [attribute keys csv|Empty for all attributes]\r\n" + "measurementAttributes = [attribute keys csv|Empty for all attributes]\r\n" + "orderBy:[ushort asc|desc, ushort asc|desc, ...]\r\n" + "searchCondition:[search filter string]\r\n" + "aggregation:[Measurements|AggregationMeasurements|Default|All]\r\n" + "mergeAttributes:[list of measurement attributes]\r\n" + "mergeCondition: [None|MeasurementsInAtLeastTwoParts|MeasurementsInAllParts]\r\n" + "mergeMasterPart: [part uuid]\r\n" + "fromModificationDate:[Date]\r\n" + "toModificationDate:[Date]"}", ex ); + throw new InvalidOperationException( $"Invalid filter value '{value}' for parameter '{key}'. The can be specified via url parameter in the form of 'key=value'. The following keys are valid: {"partUuids = [list of part uuids]\r\n" + "deep = [True|False]\r\n" + "limitResult = [short]\r\n" + "measurementUuids = [list of measurement uuids]\r\n" + "characteristicUuids = [list of characteristic uuids]\r\n" + "valueAttributes = [attribute keys csv|Empty for all attributes]\r\n" + "measurementAttributes = [attribute keys csv|Empty for all attributes]\r\n" + "orderBy:[ushort asc|desc, ushort asc|desc, ...]\r\n" + "searchCondition:[search filter string]\r\n" + "caseSensitive = [True|False]\r\n" + "aggregation:[Measurements|AggregationMeasurements|Default|All]\r\n" + "mergeAttributes:[list of measurement attributes]\r\n" + "mergeCondition: [None|MeasurementsInAtLeastTwoParts|MeasurementsInAllParts]\r\n" + "mergeMasterPart: [part uuid]\r\n" + "fromModificationDate:[Date]\r\n" + "toModificationDate:[Date]"}", ex ); } } @@ -223,6 +228,7 @@ public MeasurementValueFilterAttributesDto Clone() RequestedValueAttributes = RequestedValueAttributes, RequestedMeasurementAttributes = RequestedMeasurementAttributes, SearchCondition = SearchCondition, + CaseSensitive = CaseSensitive, MeasurementUuids = MeasurementUuids, AggregationMeasurements = AggregationMeasurements, FromModificationDate = FromModificationDate, @@ -268,6 +274,9 @@ public override IReadOnlyCollection ToParameterDefinition() if( SearchCondition != null ) result.Add( ParameterDefinition.Create( SearchConditionParamName, SearchConditionParser.GenericConditionToString( SearchCondition ) ) ); + if( CaseSensitive != null && CaseSensitive.Value ) + result.Add( ParameterDefinition.Create( CaseSensitiveParamName, CaseSensitive.ToString() ) ); + if( AggregationMeasurements != AggregationMeasurementSelectionDto.Default ) result.Add( ParameterDefinition.Create( AggregationParamName, AggregationMeasurements.ToString() ) ); From 038e2652a6afb6dcb2d4f936ca012839f801b546 Mon Sep 17 00:00:00 2001 From: Alexander Thiele Date: Fri, 10 Feb 2023 11:11:52 +0100 Subject: [PATCH 5/9] feat: small changes in GenericSearchCondition converter --- ...enericSearchConditionSerializationTests.cs | 313 +++++++++++++++++- .../GenericSearchConditionConverter.cs | 10 +- .../Data/GenericSearchConditionDto.cs | 24 +- .../JsonGenericSearchConditionDtoConverter.cs | 11 +- 4 files changed, 334 insertions(+), 24 deletions(-) diff --git a/src/Api.Rest.Dtos.Tests/Data/GenericSearchConditionSerializationTests.cs b/src/Api.Rest.Dtos.Tests/Data/GenericSearchConditionSerializationTests.cs index 4357efed..584a70e3 100644 --- a/src/Api.Rest.Dtos.Tests/Data/GenericSearchConditionSerializationTests.cs +++ b/src/Api.Rest.Dtos.Tests/Data/GenericSearchConditionSerializationTests.cs @@ -13,16 +13,25 @@ namespace Zeiss.PiWeb.Api.Rest.Dtos.Tests.Data #region usings using System.Linq; + using System.Text.Json; using Newtonsoft.Json; using NUnit.Framework; using Zeiss.PiWeb.Api.Rest.Dtos.Converter; using Zeiss.PiWeb.Api.Rest.Dtos.Data; + using Zeiss.PiWeb.Api.Rest.Dtos.JsonConverters; + using JsonSerializer = System.Text.Json.JsonSerializer; #endregion [TestFixture] public class GenericSearchConditionSerializationTests { + #region members + + private readonly JsonSerializerOptions _Options = new() { Converters = { new JsonGenericSearchConditionDtoConverter() } }; + + #endregion + #region methods [Test] @@ -38,7 +47,6 @@ public void GenericSearchConditionConverter_SerializesNotConditionProperly() } }; - var serialized = JsonConvert.SerializeObject( original, Formatting.None, new GenericSearchConditionConverter() ); var deserialized = JsonConvert.DeserializeObject( serialized, new GenericSearchConditionConverter() ); var typedDeserialized = deserialized as GenericSearchNotDto; @@ -85,6 +93,105 @@ public void GenericSearchConditionConverter_SerializesAndConditionProperly() Assert.That( serialized, Is.EqualTo( serializedAgain ) ); } + [Test] + public void GenericSearchConditionConverter_SerializesAndConditionWithDifferentConditionsProperly() + { + var original = (GenericSearchConditionDto)new GenericSearchAndDto + { + Conditions = new GenericSearchConditionDto[] + { + new GenericSearchFieldConditionDto + { + FieldName = "TestName", + Operation = OperationDto.GreaterThan, + Value = "TestValue" + }, + new GenericSearchAttributeConditionDto + { + Attribute = 4, + Operation = OperationDto.GreaterThan, + Value = "Test2Value" + } + } + }; + + var serialized = JsonConvert.SerializeObject( original, Formatting.None, new GenericSearchConditionConverter() ); + var deserialized = JsonConvert.DeserializeObject( serialized, new GenericSearchConditionConverter() ); + var typedDeserialized = deserialized as GenericSearchAndDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.Conditions, Has.Exactly( 2 ).Items ); + Assert.That( typedDeserialized.Conditions.ElementAt( 0 ), Is.TypeOf() ); + Assert.That( typedDeserialized.Conditions.ElementAt( 1 ), Is.TypeOf() ); + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + + [Test] + public void GenericSearchConditionConverter_SerializesNestedConditionProperly() + { + var original = (GenericSearchConditionDto)new GenericSearchAndDto + { + Conditions = new GenericSearchConditionDto[] + { + new GenericSearchFieldConditionDto + { + FieldName = "TestName", + Operation = OperationDto.GreaterThan, + Value = "TestValue" + }, + new GenericSearchAttributeConditionDto + { + Attribute = 4, + Operation = OperationDto.GreaterThan, + Value = "Test2Value" + }, + new GenericSearchAndDto + { + Conditions = new GenericSearchConditionDto[] + { + new GenericSearchFieldConditionDto + { + FieldName = "NestedTestName", + Operation = OperationDto.GreaterThan, + Value = "NestedTestValue" + }, + new GenericSearchAttributeConditionDto + { + Attribute = 8, + Operation = OperationDto.GreaterThan, + Value = "NestedTestValue2" + } + } + } + } + }; + + var serialized = JsonConvert.SerializeObject( original, Formatting.None, new GenericSearchConditionConverter() ); + var deserialized = JsonConvert.DeserializeObject( serialized, new GenericSearchConditionConverter() ); + var typedDeserialized = deserialized as GenericSearchAndDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.Conditions, Has.Exactly( 3 ).Items ); + Assert.That( typedDeserialized.Conditions.ElementAt( 0 ), Is.TypeOf() ); + Assert.That( typedDeserialized.Conditions.ElementAt( 1 ), Is.TypeOf() ); + Assert.That( typedDeserialized.Conditions.ElementAt( 2 ), Is.TypeOf() ); + + var nestedDeserialized = typedDeserialized.Conditions.Last() as GenericSearchAndDto; + Assert.That( nestedDeserialized, Is.Not.Null ); + + var nestedFieldCondition = nestedDeserialized.Conditions.First() as GenericSearchFieldConditionDto; + Assert.That( nestedFieldCondition, Is.Not.Null ); + Assert.That( nestedFieldCondition.FieldName, Is.EqualTo( "NestedTestName" ) ); + Assert.That( nestedFieldCondition.Value, Is.EqualTo( "NestedTestValue" ) ); + Assert.That( nestedFieldCondition.Operation, Is.EqualTo( OperationDto.GreaterThan ) ); + + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + [Test] public void GenericSearchConditionConverter_SerializesAttributeConditionProperly() { @@ -131,6 +238,210 @@ public void GenericSearchConditionConverter_SerializesFieldConditionProperly() Assert.That( serialized, Is.EqualTo( serializedAgain ) ); } + [Test] + public void JsonGenericSearchConditionConverter_SerializesNotConditionProperly() + { + var original = (GenericSearchConditionDto)new GenericSearchNotDto + { + Condition = new GenericSearchFieldConditionDto + { + FieldName = "TestName", + Operation = OperationDto.GreaterThan, + Value = "TestValue" + } + }; + + var serialized = JsonSerializer.Serialize( original, _Options ); + var deserialized = JsonSerializer.Deserialize( serialized, _Options ); + var typedDeserialized = deserialized as GenericSearchNotDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.Condition, Is.TypeOf() ); + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + + [Test] + public void JsonGenericSearchConditionConverter_SerializesAndConditionProperly() + { + var original = (GenericSearchConditionDto)new GenericSearchAndDto + { + Conditions = new[] + { + new GenericSearchFieldConditionDto + { + FieldName = "TestName", + Operation = OperationDto.GreaterThan, + Value = "TestValue" + }, + new GenericSearchFieldConditionDto + { + FieldName = "Test2Name", + Operation = OperationDto.GreaterThan, + Value = "Test2Value" + } + } + }; + + var serialized = JsonSerializer.Serialize( original, _Options ); + var deserialized = JsonSerializer.Deserialize( serialized, _Options ); + var typedDeserialized = deserialized as GenericSearchAndDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.Conditions, Has.Exactly( 2 ).Items ); + Assert.That( typedDeserialized.Conditions.ElementAt( 0 ), Is.TypeOf() ); + Assert.That( typedDeserialized.Conditions.ElementAt( 1 ), Is.TypeOf() ); + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + + [Test] + public void JsonGenericSearchConditionConverter_SerializesAndConditionWithDifferentConditionsProperly() + { + var original = (GenericSearchConditionDto)new GenericSearchAndDto + { + Conditions = new GenericSearchConditionDto[] + { + new GenericSearchFieldConditionDto + { + FieldName = "TestName", + Operation = OperationDto.GreaterThan, + Value = "TestValue" + }, + new GenericSearchAttributeConditionDto + { + Attribute = 4, + Operation = OperationDto.GreaterThan, + Value = "Test2Value" + } + } + }; + + var serialized = JsonSerializer.Serialize( original, _Options ); + var deserialized = JsonSerializer.Deserialize( serialized, _Options ); + var typedDeserialized = deserialized as GenericSearchAndDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.Conditions, Has.Exactly( 2 ).Items ); + Assert.That( typedDeserialized.Conditions.ElementAt( 0 ), Is.TypeOf() ); + Assert.That( typedDeserialized.Conditions.ElementAt( 1 ), Is.TypeOf() ); + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + + [Test] + public void JsonGenericSearchConditionConverter_SerializesNestedConditionProperly() + { + var original = (GenericSearchConditionDto)new GenericSearchAndDto + { + Conditions = new GenericSearchConditionDto[] + { + new GenericSearchFieldConditionDto + { + FieldName = "TestName", + Operation = OperationDto.GreaterThan, + Value = "TestValue" + }, + new GenericSearchAttributeConditionDto + { + Attribute = 4, + Operation = OperationDto.GreaterThan, + Value = "Test2Value" + }, + new GenericSearchAndDto + { + Conditions = new GenericSearchConditionDto[] + { + new GenericSearchFieldConditionDto + { + FieldName = "NestedTestName", + Operation = OperationDto.GreaterThan, + Value = "NestedTestValue" + }, + new GenericSearchAttributeConditionDto + { + Attribute = 8, + Operation = OperationDto.GreaterThan, + Value = "NestedTestValue2" + } + } + } + } + }; + + var serialized = JsonSerializer.Serialize( original, _Options ); + var deserialized = JsonSerializer.Deserialize( serialized, _Options ); + var typedDeserialized = deserialized as GenericSearchAndDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.Conditions, Has.Exactly( 3 ).Items ); + Assert.That( typedDeserialized.Conditions.ElementAt( 0 ), Is.TypeOf() ); + Assert.That( typedDeserialized.Conditions.ElementAt( 1 ), Is.TypeOf() ); + Assert.That( typedDeserialized.Conditions.ElementAt( 2 ), Is.TypeOf() ); + + var nestedDeserialized = typedDeserialized.Conditions.Last() as GenericSearchAndDto; + Assert.That( nestedDeserialized, Is.Not.Null ); + + var nestedFieldCondition = nestedDeserialized.Conditions.First() as GenericSearchFieldConditionDto; + Assert.That( nestedFieldCondition, Is.Not.Null ); + Assert.That( nestedFieldCondition.FieldName, Is.EqualTo( "NestedTestName" ) ); + Assert.That( nestedFieldCondition.Value, Is.EqualTo( "NestedTestValue" ) ); + Assert.That( nestedFieldCondition.Operation, Is.EqualTo( OperationDto.GreaterThan ) ); + + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + + [Test] + public void JsonGenericSearchConditionConverter_SerializesAttributeConditionProperly() + { + var original = new GenericSearchAttributeConditionDto + { + Attribute = 123, + Operation = OperationDto.GreaterThan, + Value = "TestValue" + }; + + var serialized = JsonSerializer.Serialize( original, _Options ); + var deserialized = JsonSerializer.Deserialize( serialized, _Options ); + var typedDeserialized = deserialized as GenericSearchAttributeConditionDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.Attribute, Is.EqualTo( 123 ) ); + Assert.That( typedDeserialized.Operation, Is.EqualTo( OperationDto.GreaterThan ) ); + Assert.That( typedDeserialized.Value, Is.EqualTo( "TestValue" ) ); + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + + [Test] + public void JsonGenericSearchConditionConverter_SerializesFieldConditionProperly() + { + var original = new GenericSearchFieldConditionDto + { + FieldName = "TestName", + Operation = OperationDto.GreaterThan, + Value = "TestValue" + }; + + var serialized = JsonSerializer.Serialize( original, _Options ); + var deserialized = JsonSerializer.Deserialize( serialized, _Options ); + var typedDeserialized = deserialized as GenericSearchFieldConditionDto; + var serializedAgain = JsonConvert.SerializeObject( deserialized, Formatting.None, new GenericSearchConditionConverter() ); + + Assert.That( deserialized, Is.TypeOf() ); + Assert.That( typedDeserialized, Is.Not.Null ); + Assert.That( typedDeserialized.FieldName, Is.EqualTo( "TestName" ) ); + Assert.That( typedDeserialized.Operation, Is.EqualTo( OperationDto.GreaterThan ) ); + Assert.That( typedDeserialized.Value, Is.EqualTo( "TestValue" ) ); + Assert.That( serialized, Is.EqualTo( serializedAgain ) ); + } + #endregion } } \ No newline at end of file diff --git a/src/Api.Rest.Dtos/Converter/GenericSearchConditionConverter.cs b/src/Api.Rest.Dtos/Converter/GenericSearchConditionConverter.cs index 0dd6c658..c86efa67 100644 --- a/src/Api.Rest.Dtos/Converter/GenericSearchConditionConverter.cs +++ b/src/Api.Rest.Dtos/Converter/GenericSearchConditionConverter.cs @@ -3,7 +3,7 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * */ /* Carl Zeiss IMT (IZfM Dresden) */ /* Softwaresystem PiWeb */ -/* (c) Carl Zeiss 2015 */ +/* (c) Carl Zeiss 2023 */ /* * * * * * * * * * * * * * * * * * * * * * * * * */ #endregion @@ -64,16 +64,16 @@ private static GenericSearchConditionDto ReadJson( JToken jToken, JsonSerializer var fields = CollectFields( jToken ); - if( fields.Contains( GenericSearchConditionDto.ConditionFieldName ) ) + if( fields.Contains( GenericSearchConditionDto.NotDiscriminator ) ) return serializer.Deserialize( jsonReader ); - if( fields.Contains( GenericSearchConditionDto.ConditionsFieldName ) ) + if( fields.Contains( GenericSearchConditionDto.AndDiscriminator ) ) return serializer.Deserialize( jsonReader ); - if( fields.Contains( GenericSearchConditionDto.FieldNameFieldName ) ) + if( fields.Contains( GenericSearchConditionDto.FieldConditionDiscriminator ) ) return serializer.Deserialize( jsonReader ); - if( fields.Contains( GenericSearchConditionDto.AttributeFieldName ) ) + if( fields.Contains( GenericSearchConditionDto.AttributeConditionDiscriminator ) ) return serializer.Deserialize( jsonReader ); return null; diff --git a/src/Api.Rest.Dtos/Data/GenericSearchConditionDto.cs b/src/Api.Rest.Dtos/Data/GenericSearchConditionDto.cs index 50e14340..5d05ca83 100644 --- a/src/Api.Rest.Dtos/Data/GenericSearchConditionDto.cs +++ b/src/Api.Rest.Dtos/Data/GenericSearchConditionDto.cs @@ -29,10 +29,10 @@ public class GenericSearchConditionDto { #region constants - internal const string ConditionFieldName = "condition"; - internal const string ConditionsFieldName = "conditions"; - internal const string AttributeFieldName = "attribute"; - internal const string FieldNameFieldName = "fieldName"; + internal const string NotDiscriminator = "condition"; + internal const string AndDiscriminator = "conditions"; + internal const string AttributeConditionDiscriminator = "attribute"; + internal const string FieldConditionDiscriminator = "fieldName"; #endregion } @@ -41,8 +41,8 @@ public class GenericSearchNotDto : GenericSearchConditionDto { #region properties - [JsonProperty( ConditionFieldName )] - [JsonPropertyName( ConditionFieldName )] + [JsonProperty( NotDiscriminator )] + [JsonPropertyName( NotDiscriminator )] public GenericSearchConditionDto Condition { get; set; } #endregion @@ -70,8 +70,8 @@ public GenericSearchAndDto( IReadOnlyCollection filte #region properties - [JsonProperty( ConditionsFieldName )] - [JsonPropertyName( ConditionsFieldName )] + [JsonProperty( AndDiscriminator )] + [JsonPropertyName( AndDiscriminator )] public IReadOnlyCollection Conditions { get; set; } #endregion @@ -96,8 +96,8 @@ public class GenericSearchAttributeConditionDto : GenericSearchValueConditionDto { #region properties - [JsonProperty( AttributeFieldName )] - [JsonPropertyName( AttributeFieldName )] + [JsonProperty( AttributeConditionDiscriminator )] + [JsonPropertyName( AttributeConditionDiscriminator )] public ushort Attribute { get; set; } #endregion @@ -107,8 +107,8 @@ public class GenericSearchFieldConditionDto : GenericSearchValueConditionDto { #region properties - [JsonProperty( FieldNameFieldName )] - [JsonPropertyName( FieldNameFieldName )] + [JsonProperty( FieldConditionDiscriminator )] + [JsonPropertyName( FieldConditionDiscriminator )] public string FieldName { get; set; } #endregion diff --git a/src/Api.Rest.Dtos/JsonConverters/JsonGenericSearchConditionDtoConverter.cs b/src/Api.Rest.Dtos/JsonConverters/JsonGenericSearchConditionDtoConverter.cs index 1d1629ae..ce582b8c 100644 --- a/src/Api.Rest.Dtos/JsonConverters/JsonGenericSearchConditionDtoConverter.cs +++ b/src/Api.Rest.Dtos/JsonConverters/JsonGenericSearchConditionDtoConverter.cs @@ -3,7 +3,7 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * */ /* Carl Zeiss IMT (IZfM Dresden) */ /* Softwaresystem PiWeb */ -/* (c) Carl Zeiss 2022 */ +/* (c) Carl Zeiss 2023 */ /* * * * * * * * * * * * * * * * * * * * * * * * * */ #endregion @@ -64,22 +64,21 @@ public override GenericSearchConditionDto Read( ref Utf8JsonReader reader, Type { var propertyNames = CollectPropertyNames( reader ); - if( propertyNames.Contains( GenericSearchConditionDto.ConditionFieldName ) ) + if( propertyNames.Contains( GenericSearchConditionDto.NotDiscriminator ) ) return JsonSerializer.Deserialize( ref reader, options ); - if( propertyNames.Contains( GenericSearchConditionDto.ConditionsFieldName ) ) + if( propertyNames.Contains( GenericSearchConditionDto.AndDiscriminator ) ) return JsonSerializer.Deserialize( ref reader, options ); - if( propertyNames.Contains( GenericSearchConditionDto.AttributeFieldName ) ) + if( propertyNames.Contains( GenericSearchConditionDto.AttributeConditionDiscriminator ) ) return JsonSerializer.Deserialize( ref reader, options ); - if( propertyNames.Contains( GenericSearchConditionDto.FieldNameFieldName ) ) + if( propertyNames.Contains( GenericSearchConditionDto.FieldConditionDiscriminator ) ) return JsonSerializer.Deserialize( ref reader, options ); throw new NotImplementedException( "Encountered unknown search condition type" ); } - [NotNull] private static HashSet CollectPropertyNames( Utf8JsonReader reader ) { From c9d1ff5da8ff8c27d93314742f23ed8ac29a2d04 Mon Sep 17 00:00:00 2001 From: Alexander Thiele Date: Tue, 14 Feb 2023 09:14:28 +0100 Subject: [PATCH 6/9] refactor: make case sensitivity optional in parse method - case sensitivity is now optional in parse method of filter classes - fixed line breaks --- .../Data/MeasurementFilterAttributesDto.cs | 546 +++++++++--------- .../MeasurementValueFilterAttributesDto.cs | 4 +- 2 files changed, 275 insertions(+), 275 deletions(-) diff --git a/src/Api.Rest.Dtos/Data/MeasurementFilterAttributesDto.cs b/src/Api.Rest.Dtos/Data/MeasurementFilterAttributesDto.cs index 67bb4d59..5c2d60df 100644 --- a/src/Api.Rest.Dtos/Data/MeasurementFilterAttributesDto.cs +++ b/src/Api.Rest.Dtos/Data/MeasurementFilterAttributesDto.cs @@ -1,4 +1,4 @@ -#region copyright +#region copyright /* * * * * * * * * * * * * * * * * * * * * * * * * */ /* Carl Zeiss IMT (IZfM Dresden) */ @@ -6,308 +6,308 @@ /* (c) Carl Zeiss 2015 */ /* * * * * * * * * * * * * * * * * * * * * * * * * */ -#endregion +#endregion -namespace Zeiss.PiWeb.Api.Rest.Dtos.Data +namespace Zeiss.PiWeb.Api.Rest.Dtos.Data { - #region usings + #region usings - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; using System.Xml; - #endregion + #endregion - /// - /// This class contains a measurement search criteria for a measurement search. - /// - public class MeasurementFilterAttributesDto : AbstractMeasurementFilterAttributesDto + /// + /// This class contains a measurement search criteria for a measurement search. + /// + public class MeasurementFilterAttributesDto : AbstractMeasurementFilterAttributesDto { - #region constants + #region constants - private const string RequestedMeasurementAttributesParamName = "requestedmeasurementattributes"; - private const string StatisticsParamName = "statistics"; - - private const string MergeAttributesParamName = "mergeattributes"; - private const string MergeConditionParamName = "mergecondition"; + private const string RequestedMeasurementAttributesParamName = "requestedmeasurementattributes"; + private const string StatisticsParamName = "statistics"; + + private const string MergeAttributesParamName = "mergeattributes"; + private const string MergeConditionParamName = "mergecondition"; private const string MergeMasterPartParamName = "mergemasterpart"; - #endregion - - #region constructors - - /// - /// Initializes a new instance of the class. - /// - public MeasurementFilterAttributesDto() - { - RequestedMeasurementAttributes = new AttributeSelector( AllAttributeSelectionDto.True ); - - MergeCondition = MeasurementMergeConditionDto.MeasurementsInAllParts; - MergeMasterPart = null; - - Statistics = MeasurementStatisticsDto.None; + #endregion + + #region constructors + + /// + /// Initializes a new instance of the class. + /// + public MeasurementFilterAttributesDto() + { + RequestedMeasurementAttributes = new AttributeSelector( AllAttributeSelectionDto.True ); + + MergeCondition = MeasurementMergeConditionDto.MeasurementsInAllParts; + MergeMasterPart = null; + + Statistics = MeasurementStatisticsDto.None; } - #endregion + #endregion - #region properties + #region properties - /// - /// Gets or sets the selector for the measurement attributes. - /// + /// + /// Gets or sets the selector for the measurement attributes. + /// public AttributeSelector RequestedMeasurementAttributes { get; set; } - /// - /// Specifies if statistical information (: number characteristics OOT, OOT, etc.) should be returned. - /// + /// + /// Specifies if statistical information (: number characteristics OOT, OOT, etc.) should be returned. + /// public MeasurementStatisticsDto Statistics { get; set; } - /// - /// Specifies the list of primary measurement keys to be used for joining measurements accross multiple parts on the server side. - /// + /// + /// Specifies the list of primary measurement keys to be used for joining measurements accross multiple parts on the server side. + /// public IReadOnlyList MergeAttributes { get; set; } - /// - /// Specifies the condition that must be adhered to - /// when merging measurements accross multiple parts using a primary key. - /// Default value is MeasurementMergeCondition.MeasurementsInAllParts. - /// + /// + /// Specifies the condition that must be adhered to + /// when merging measurements accross multiple parts using a primary key. + /// Default value is MeasurementMergeCondition.MeasurementsInAllParts. + /// public MeasurementMergeConditionDto MergeCondition { get; set; } - /// - /// Specifies the part to be used as master part - /// when merging measurements accross multiple parts using a primary key. - /// Default value is null. - /// + /// + /// Specifies the part to be used as master part + /// when merging measurements accross multiple parts using a primary key. + /// Default value is null. + /// public Guid? MergeMasterPart { get; set; } - #endregion - - #region methods - - /// - /// Parses the filter and returns a object that represents the filter values. - /// If the parse operation was not successful, an will be thrown. - /// - /// The with the parsed information. - public static MeasurementFilterAttributesDto Parse( - string partUuids, - string measurementUuids, - string deep, - string limitResult, - string order, - string requestedMeasurementAttributes, - string searchCondition, - string caseSensitive, - string statistics, - string aggregation, - string fromModificationDate, - string toModificationDate, - string mergeAttributes, - string mergeCondition, - string mergeMasterPart, - string limitResultPerPart = "-1" ) - { - var items = new[] - { - ValueTuple.Create( PartUuidsParamName, partUuids ), - ValueTuple.Create( MeasurementUuidsParamName, measurementUuids ), - ValueTuple.Create( DeepParamName, deep ), - ValueTuple.Create( LimitResultParamName, limitResult ), - ValueTuple.Create( LimitResultPerPartParamName, limitResultPerPart ), - ValueTuple.Create( OrderByParamName, order ), - ValueTuple.Create( RequestedMeasurementAttributesParamName, requestedMeasurementAttributes ), - ValueTuple.Create( SearchConditionParamName, searchCondition ), - ValueTuple.Create( CaseSensitiveParamName, caseSensitive ), - ValueTuple.Create( StatisticsParamName, statistics ), - ValueTuple.Create( AggregationParamName, aggregation ), - ValueTuple.Create( FromModificationDateParamName, fromModificationDate ), - ValueTuple.Create( ToModificationDateParamName, toModificationDate ), - ValueTuple.Create( MergeAttributesParamName, mergeAttributes ), - ValueTuple.Create( MergeConditionParamName, mergeCondition ), - ValueTuple.Create( MergeMasterPartParamName, mergeMasterPart ) - }; - - var result = new MeasurementFilterAttributesDto(); - foreach( var (key, value) in items ) - { - if( string.IsNullOrEmpty( value ) ) - continue; - - try - { - switch( key ) - { - case PartUuidsParamName: - result.PartUuids = RestClientHelper.ConvertStringToGuidList( value ); - break; - case DeepParamName: - result.Deep = bool.Parse( value ); - break; - case MeasurementUuidsParamName: - result.MeasurementUuids = RestClientHelper.ConvertStringToGuidList( value ); - break; - case LimitResultParamName: - result.LimitResult = int.Parse( value, CultureInfo.InvariantCulture ); - break; - case LimitResultPerPartParamName: - result.LimitResultPerPart = int.Parse( value, CultureInfo.InvariantCulture ); - break; - case RequestedMeasurementAttributesParamName: - result.RequestedMeasurementAttributes = new AttributeSelector( RestClientHelper.ConvertStringToUInt16List( value ) ); - break; - case OrderByParamName: - result.OrderBy = value.Split( ',' ).Select( element => OrderDtoParser.Parse( element, EntityDto.Measurement ) ).ToArray(); - break; - case SearchConditionParamName: - result.SearchCondition = SearchConditionParser.Parse( value ); - break; - case CaseSensitiveParamName: - result.CaseSensitive = bool.Parse( value ); - break; - case StatisticsParamName: - result.Statistics = (MeasurementStatisticsDto)Enum.Parse( typeof( MeasurementStatisticsDto ), value ); - break; - case AggregationParamName: - result.AggregationMeasurements = (AggregationMeasurementSelectionDto)Enum.Parse( typeof( AggregationMeasurementSelectionDto ), value ); - break; - case ToModificationDateParamName: - result.ToModificationDate = XmlConvert.ToDateTime( value, XmlDateTimeSerializationMode.RoundtripKind ); - break; - case FromModificationDateParamName: - result.FromModificationDate = XmlConvert.ToDateTime( value, XmlDateTimeSerializationMode.RoundtripKind ); - break; - case MergeAttributesParamName: - result.MergeAttributes = RestClientHelper.ConvertStringToUInt16List( value ); - break; - case MergeConditionParamName: - result.MergeCondition = (MeasurementMergeConditionDto)Enum.Parse( typeof( MeasurementMergeConditionDto ), value ); - break; - case MergeMasterPartParamName: - result.MergeMasterPart = string.IsNullOrWhiteSpace( value ) ? (Guid?)null : Guid.Parse( value ); - break; - } - } - catch( Exception ex ) - { - throw new InvalidOperationException( $"Invalid filter value '{value}' for parameter '{key}'. The can be specified via url parameter in the form of 'key=value'. The following keys are valid: {"partUuids: [list of part uuids]\r\n" + "deep = [True|False]\r\n" + "limitResult: [short]\r\n" + "measurementUuids: [list of measurement uuids]\r\n" + "measurementAttributes: [attribute keys csv|Empty for all attributes]\r\n" + "orderBy:[ushort asc|desc, ushort asc|desc, ...]\r\n" + "searchCondition:[search filter string]\r\n" + "caseSensitive: [True|False]\r\n" + "aggregation:[Measurements|AggregationMeasurements|Default|All]\r\n" + "statistics:[None|Simple|Detailed]\r\n" + "mergeAttributes:[list of measurement attributes]\r\n" + "mergeCondition: [None|MeasurementsInAtLeastTwoParts|MeasurementsInAllParts]\r\n" + "mergeMasterPart: [part uuid]\r\n" + "fromModificationDate:[Date]\r\n" + "toModificationDate:[Date]"}", ex ); - } - } - - return result; + #endregion + + #region methods + + /// + /// Parses the filter and returns a object that represents the filter values. + /// If the parse operation was not successful, an will be thrown. + /// + /// The with the parsed information. + public static MeasurementFilterAttributesDto Parse( + string partUuids, + string measurementUuids, + string deep, + string limitResult, + string order, + string requestedMeasurementAttributes, + string searchCondition, + string statistics, + string aggregation, + string fromModificationDate, + string toModificationDate, + string mergeAttributes, + string mergeCondition, + string mergeMasterPart, + string limitResultPerPart = "-1", + string caseSensitive = null ) + { + var items = new[] + { + ValueTuple.Create( PartUuidsParamName, partUuids ), + ValueTuple.Create( MeasurementUuidsParamName, measurementUuids ), + ValueTuple.Create( DeepParamName, deep ), + ValueTuple.Create( LimitResultParamName, limitResult ), + ValueTuple.Create( LimitResultPerPartParamName, limitResultPerPart ), + ValueTuple.Create( OrderByParamName, order ), + ValueTuple.Create( RequestedMeasurementAttributesParamName, requestedMeasurementAttributes ), + ValueTuple.Create( SearchConditionParamName, searchCondition ), + ValueTuple.Create( CaseSensitiveParamName, caseSensitive ), + ValueTuple.Create( StatisticsParamName, statistics ), + ValueTuple.Create( AggregationParamName, aggregation ), + ValueTuple.Create( FromModificationDateParamName, fromModificationDate ), + ValueTuple.Create( ToModificationDateParamName, toModificationDate ), + ValueTuple.Create( MergeAttributesParamName, mergeAttributes ), + ValueTuple.Create( MergeConditionParamName, mergeCondition ), + ValueTuple.Create( MergeMasterPartParamName, mergeMasterPart ) + }; + + var result = new MeasurementFilterAttributesDto(); + foreach( var (key, value) in items ) + { + if( string.IsNullOrEmpty( value ) ) + continue; + + try + { + switch( key ) + { + case PartUuidsParamName: + result.PartUuids = RestClientHelper.ConvertStringToGuidList( value ); + break; + case DeepParamName: + result.Deep = bool.Parse( value ); + break; + case MeasurementUuidsParamName: + result.MeasurementUuids = RestClientHelper.ConvertStringToGuidList( value ); + break; + case LimitResultParamName: + result.LimitResult = int.Parse( value, CultureInfo.InvariantCulture ); + break; + case LimitResultPerPartParamName: + result.LimitResultPerPart = int.Parse( value, CultureInfo.InvariantCulture ); + break; + case RequestedMeasurementAttributesParamName: + result.RequestedMeasurementAttributes = new AttributeSelector( RestClientHelper.ConvertStringToUInt16List( value ) ); + break; + case OrderByParamName: + result.OrderBy = value.Split( ',' ).Select( element => OrderDtoParser.Parse( element, EntityDto.Measurement ) ).ToArray(); + break; + case SearchConditionParamName: + result.SearchCondition = SearchConditionParser.Parse( value ); + break; + case CaseSensitiveParamName: + result.CaseSensitive = bool.Parse( value ); + break; + case StatisticsParamName: + result.Statistics = (MeasurementStatisticsDto)Enum.Parse( typeof( MeasurementStatisticsDto ), value ); + break; + case AggregationParamName: + result.AggregationMeasurements = (AggregationMeasurementSelectionDto)Enum.Parse( typeof( AggregationMeasurementSelectionDto ), value ); + break; + case ToModificationDateParamName: + result.ToModificationDate = XmlConvert.ToDateTime( value, XmlDateTimeSerializationMode.RoundtripKind ); + break; + case FromModificationDateParamName: + result.FromModificationDate = XmlConvert.ToDateTime( value, XmlDateTimeSerializationMode.RoundtripKind ); + break; + case MergeAttributesParamName: + result.MergeAttributes = RestClientHelper.ConvertStringToUInt16List( value ); + break; + case MergeConditionParamName: + result.MergeCondition = (MeasurementMergeConditionDto)Enum.Parse( typeof( MeasurementMergeConditionDto ), value ); + break; + case MergeMasterPartParamName: + result.MergeMasterPart = string.IsNullOrWhiteSpace( value ) ? (Guid?)null : Guid.Parse( value ); + break; + } + } + catch( Exception ex ) + { + throw new InvalidOperationException( $"Invalid filter value '{value}' for parameter '{key}'. The can be specified via url parameter in the form of 'key=value'. The following keys are valid: {"partUuids: [list of part uuids]\r\n" + "deep = [True|False]\r\n" + "limitResult: [short]\r\n" + "measurementUuids: [list of measurement uuids]\r\n" + "measurementAttributes: [attribute keys csv|Empty for all attributes]\r\n" + "orderBy:[ushort asc|desc, ushort asc|desc, ...]\r\n" + "searchCondition:[search filter string]\r\n" + "caseSensitive: [True|False]\r\n" + "aggregation:[Measurements|AggregationMeasurements|Default|All]\r\n" + "statistics:[None|Simple|Detailed]\r\n" + "mergeAttributes:[list of measurement attributes]\r\n" + "mergeCondition: [None|MeasurementsInAtLeastTwoParts|MeasurementsInAllParts]\r\n" + "mergeMasterPart: [part uuid]\r\n" + "fromModificationDate:[Date]\r\n" + "toModificationDate:[Date]"}", ex ); + } + } + + return result; } - /// - /// Converts this filter object into an equivalent object. - /// - public MeasurementValueFilterAttributesDto ToMeasurementValueFilterAttributes() - { - if( Statistics != MeasurementStatisticsDto.None ) - throw new InvalidOperationException( "Unable to create a 'MeasurementValueFilterAttributes' object when MeasurementFilterAttributes.Statistics is not MeasurementStatistics.None" ); - - return new MeasurementValueFilterAttributesDto - { - PartUuids = PartUuids, - Deep = Deep, - LimitResult = LimitResult, - LimitResultPerPart = LimitResultPerPart, - OrderBy = OrderBy, - RequestedMeasurementAttributes = RequestedMeasurementAttributes, - SearchCondition = SearchCondition, - MeasurementUuids = MeasurementUuids, - AggregationMeasurements = AggregationMeasurements, - FromModificationDate = FromModificationDate, - ToModificationDate = ToModificationDate, - MergeAttributes = MergeAttributes, - MergeCondition = MergeCondition, - MergeMasterPart = MergeMasterPart - }; + /// + /// Converts this filter object into an equivalent object. + /// + public MeasurementValueFilterAttributesDto ToMeasurementValueFilterAttributes() + { + if( Statistics != MeasurementStatisticsDto.None ) + throw new InvalidOperationException( "Unable to create a 'MeasurementValueFilterAttributes' object when MeasurementFilterAttributes.Statistics is not MeasurementStatistics.None" ); + + return new MeasurementValueFilterAttributesDto + { + PartUuids = PartUuids, + Deep = Deep, + LimitResult = LimitResult, + LimitResultPerPart = LimitResultPerPart, + OrderBy = OrderBy, + RequestedMeasurementAttributes = RequestedMeasurementAttributes, + SearchCondition = SearchCondition, + MeasurementUuids = MeasurementUuids, + AggregationMeasurements = AggregationMeasurements, + FromModificationDate = FromModificationDate, + ToModificationDate = ToModificationDate, + MergeAttributes = MergeAttributes, + MergeCondition = MergeCondition, + MergeMasterPart = MergeMasterPart + }; } - /// - /// Creates a shallow clone of this filter. - /// - public MeasurementFilterAttributesDto Clone() - { - return new MeasurementFilterAttributesDto - { - PartUuids = PartUuids, - Deep = Deep, - LimitResult = LimitResult, - LimitResultPerPart = LimitResultPerPart, - OrderBy = OrderBy, - RequestedMeasurementAttributes = RequestedMeasurementAttributes, - SearchCondition = SearchCondition, - CaseSensitive = CaseSensitive, - MeasurementUuids = MeasurementUuids, - AggregationMeasurements = AggregationMeasurements, - FromModificationDate = FromModificationDate, - ToModificationDate = ToModificationDate, - MergeAttributes = MergeAttributes, - MergeCondition = MergeCondition, - MergeMasterPart = MergeMasterPart - }; + /// + /// Creates a shallow clone of this filter. + /// + public MeasurementFilterAttributesDto Clone() + { + return new MeasurementFilterAttributesDto + { + PartUuids = PartUuids, + Deep = Deep, + LimitResult = LimitResult, + LimitResultPerPart = LimitResultPerPart, + OrderBy = OrderBy, + RequestedMeasurementAttributes = RequestedMeasurementAttributes, + SearchCondition = SearchCondition, + CaseSensitive = CaseSensitive, + MeasurementUuids = MeasurementUuids, + AggregationMeasurements = AggregationMeasurements, + FromModificationDate = FromModificationDate, + ToModificationDate = ToModificationDate, + MergeAttributes = MergeAttributes, + MergeCondition = MergeCondition, + MergeMasterPart = MergeMasterPart + }; } - /// - public override IReadOnlyCollection ToParameterDefinition() - { - var result = new List(); - - if( PartUuids != null && PartUuids.Count > 0 ) - result.Add( ParameterDefinition.Create( PartUuidsParamName, RestClientHelper.ConvertGuidListToString( PartUuids ) ) ); - - if( Deep ) - result.Add( ParameterDefinition.Create( DeepParamName, Deep.ToString() ) ); - - if( LimitResult >= 0 ) - result.Add( ParameterDefinition.Create( LimitResultParamName, LimitResult.ToString() ) ); - - if( LimitResultPerPart >= 0 ) - result.Add( ParameterDefinition.Create( LimitResultPerPartParamName, LimitResultPerPart.ToString() ) ); - - if( MeasurementUuids != null && MeasurementUuids.Count > 0 ) - result.Add( ParameterDefinition.Create( MeasurementUuidsParamName, RestClientHelper.ConvertGuidListToString( MeasurementUuids ) ) ); - - if( RequestedMeasurementAttributes != null && RequestedMeasurementAttributes.AllAttributes != AllAttributeSelectionDto.True && RequestedMeasurementAttributes.Attributes != null ) - result.Add( ParameterDefinition.Create( RequestedMeasurementAttributesParamName, RestClientHelper.ConvertUshortArrayToString( RequestedMeasurementAttributes.Attributes ) ) ); - - if( OrderBy != null && OrderBy.Count > 0 ) - result.Add( ParameterDefinition.Create( OrderByParamName, OrderByToString( OrderBy ) ) ); - - if( SearchCondition != null ) - result.Add( ParameterDefinition.Create( SearchConditionParamName, SearchConditionParser.GenericConditionToString( SearchCondition ) ) ); - - if( CaseSensitive != null && CaseSensitive.Value ) - result.Add( ParameterDefinition.Create( CaseSensitiveParamName, CaseSensitive.ToString() ) ); - - if( Statistics != MeasurementStatisticsDto.None ) - result.Add( ParameterDefinition.Create( StatisticsParamName, Statistics.ToString() ) ); - - if( AggregationMeasurements != AggregationMeasurementSelectionDto.Default ) - result.Add( ParameterDefinition.Create( AggregationParamName, AggregationMeasurements.ToString() ) ); - - if( FromModificationDate.HasValue ) - result.Add( ParameterDefinition.Create( FromModificationDateParamName, XmlConvert.ToString( FromModificationDate.Value, XmlDateTimeSerializationMode.RoundtripKind ) ) ); - - if( ToModificationDate.HasValue ) - result.Add( ParameterDefinition.Create( ToModificationDateParamName, XmlConvert.ToString( ToModificationDate.Value, XmlDateTimeSerializationMode.RoundtripKind ) ) ); - - if( MergeAttributes != null && MergeAttributes.Count > 0 ) - result.Add( ParameterDefinition.Create( MergeAttributesParamName, RestClientHelper.ConvertUshortArrayToString( MergeAttributes ) ) ); - - if( MergeCondition != MeasurementMergeConditionDto.MeasurementsInAllParts ) - result.Add( ParameterDefinition.Create( MergeConditionParamName, MergeCondition.ToString() ) ); - - if( MergeMasterPart != null ) - result.Add( ParameterDefinition.Create( MergeMasterPartParamName, MergeMasterPart.ToString() ) ); - - return result; + /// + public override IReadOnlyCollection ToParameterDefinition() + { + var result = new List(); + + if( PartUuids != null && PartUuids.Count > 0 ) + result.Add( ParameterDefinition.Create( PartUuidsParamName, RestClientHelper.ConvertGuidListToString( PartUuids ) ) ); + + if( Deep ) + result.Add( ParameterDefinition.Create( DeepParamName, Deep.ToString() ) ); + + if( LimitResult >= 0 ) + result.Add( ParameterDefinition.Create( LimitResultParamName, LimitResult.ToString() ) ); + + if( LimitResultPerPart >= 0 ) + result.Add( ParameterDefinition.Create( LimitResultPerPartParamName, LimitResultPerPart.ToString() ) ); + + if( MeasurementUuids != null && MeasurementUuids.Count > 0 ) + result.Add( ParameterDefinition.Create( MeasurementUuidsParamName, RestClientHelper.ConvertGuidListToString( MeasurementUuids ) ) ); + + if( RequestedMeasurementAttributes != null && RequestedMeasurementAttributes.AllAttributes != AllAttributeSelectionDto.True && RequestedMeasurementAttributes.Attributes != null ) + result.Add( ParameterDefinition.Create( RequestedMeasurementAttributesParamName, RestClientHelper.ConvertUshortArrayToString( RequestedMeasurementAttributes.Attributes ) ) ); + + if( OrderBy != null && OrderBy.Count > 0 ) + result.Add( ParameterDefinition.Create( OrderByParamName, OrderByToString( OrderBy ) ) ); + + if( SearchCondition != null ) + result.Add( ParameterDefinition.Create( SearchConditionParamName, SearchConditionParser.GenericConditionToString( SearchCondition ) ) ); + + if( CaseSensitive != null && CaseSensitive.Value ) + result.Add( ParameterDefinition.Create( CaseSensitiveParamName, CaseSensitive.ToString() ) ); + + if( Statistics != MeasurementStatisticsDto.None ) + result.Add( ParameterDefinition.Create( StatisticsParamName, Statistics.ToString() ) ); + + if( AggregationMeasurements != AggregationMeasurementSelectionDto.Default ) + result.Add( ParameterDefinition.Create( AggregationParamName, AggregationMeasurements.ToString() ) ); + + if( FromModificationDate.HasValue ) + result.Add( ParameterDefinition.Create( FromModificationDateParamName, XmlConvert.ToString( FromModificationDate.Value, XmlDateTimeSerializationMode.RoundtripKind ) ) ); + + if( ToModificationDate.HasValue ) + result.Add( ParameterDefinition.Create( ToModificationDateParamName, XmlConvert.ToString( ToModificationDate.Value, XmlDateTimeSerializationMode.RoundtripKind ) ) ); + + if( MergeAttributes != null && MergeAttributes.Count > 0 ) + result.Add( ParameterDefinition.Create( MergeAttributesParamName, RestClientHelper.ConvertUshortArrayToString( MergeAttributes ) ) ); + + if( MergeCondition != MeasurementMergeConditionDto.MeasurementsInAllParts ) + result.Add( ParameterDefinition.Create( MergeConditionParamName, MergeCondition.ToString() ) ); + + if( MergeMasterPart != null ) + result.Add( ParameterDefinition.Create( MergeMasterPartParamName, MergeMasterPart.ToString() ) ); + + return result; } - #endregion - } + #endregion + } } \ No newline at end of file diff --git a/src/Api.Rest.Dtos/Data/MeasurementValueFilterAttributesDto.cs b/src/Api.Rest.Dtos/Data/MeasurementValueFilterAttributesDto.cs index 06b4826e..0c7c9a7a 100644 --- a/src/Api.Rest.Dtos/Data/MeasurementValueFilterAttributesDto.cs +++ b/src/Api.Rest.Dtos/Data/MeasurementValueFilterAttributesDto.cs @@ -110,14 +110,14 @@ public static MeasurementValueFilterAttributesDto Parse( string requestedMeasurementAttributes, string requestedValueAttributes, string searchCondition, - string caseSensitive, string aggregation, string fromModificationDate, string toModificationDate, string mergeAttributes, string mergeCondition, string mergeMasterPart, - string limitResultPerPart = "-1" ) + string limitResultPerPart = "-1", + string caseSensitive = null ) { var items = new[] { From 358f6aa8a9081f93d55c648f49157f94d38d7644 Mon Sep 17 00:00:00 2001 From: Alexander Thiele Date: Tue, 28 Feb 2023 10:27:03 +0100 Subject: [PATCH 7/9] (MINOR) chore: Add version check for cases sensitivity feature --- .../Contracts/DataServiceFeatureMatrix.cs | 5 +++++ .../HttpClient/Data/DataServiceRestClient.cs | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/Api.Rest/Contracts/DataServiceFeatureMatrix.cs b/src/Api.Rest/Contracts/DataServiceFeatureMatrix.cs index 84f3d392..986cf87d 100644 --- a/src/Api.Rest/Contracts/DataServiceFeatureMatrix.cs +++ b/src/Api.Rest/Contracts/DataServiceFeatureMatrix.cs @@ -55,6 +55,9 @@ public class DataServiceFeatureMatrix : FeatureMatrix // Request using a limit per part when fetching measurements. public static readonly Version LimitResultPerPartMinVersion = new Version( 1, 8 ); + // Compare the search condition for strings case sensitive. + public static readonly Version CaseSensitiveAttributeSearchMinVersion = new Version( 1, 10 ); + #endregion #region constructors @@ -90,6 +93,8 @@ public DataServiceFeatureMatrix( [NotNull] InterfaceVersionRange interfaceVersio public bool SupportsLimitResultPerPart => CurrentInterfaceVersion >= LimitResultPerPartMinVersion; + public bool SupportsCaseSensitiveAttributeSearch => CurrentInterfaceVersion >= CaseSensitiveAttributeSearchMinVersion; + #endregion } } \ No newline at end of file diff --git a/src/Api.Rest/HttpClient/Data/DataServiceRestClient.cs b/src/Api.Rest/HttpClient/Data/DataServiceRestClient.cs index 7f16e853..cebefa86 100644 --- a/src/Api.Rest/HttpClient/Data/DataServiceRestClient.cs +++ b/src/Api.Rest/HttpClient/Data/DataServiceRestClient.cs @@ -422,6 +422,21 @@ private async Task ThrowOnUnsupportedClearPart( CancellationToken cancellationTo } } + private async Task ThrowOnUnsupportedCaseSensitiveSearch( AbstractMeasurementFilterAttributesDto filter, CancellationToken cancellationToken ) + { + if( filter.CaseSensitive.HasValue ) + { + var featureMatrix = await GetFeatureMatrixInternal( FetchBehavior.FetchIfNotCached, cancellationToken ).ConfigureAwait( false ); + if( !featureMatrix.SupportsCaseSensitiveAttributeSearch ) + { + throw new OperationNotSupportedOnServerException( + "Specifying case sensitivity for search conditions is not supported by this server.", + DataServiceFeatureMatrix.CaseSensitiveAttributeSearchMinVersion, + featureMatrix.CurrentInterfaceVersion ); + } + } + } + #endregion #region interface IDataServiceRestClient @@ -805,6 +820,7 @@ async Task CreateCharacteristics() await ThrowOnUnsupportedMergeAttributes( filter?.MergeAttributes, cancellationToken ); await ThrowOnUnsupportedMergeMasterPart( filter, cancellationToken ); await ThrowOnUnsupportedLimitResultPerPart( filter, cancellationToken ); + await ThrowOnUnsupportedCaseSensitiveSearch( filter, cancellationToken ); const string requestPath = "measurements"; @@ -908,6 +924,7 @@ await Parallel.ForEachAsync( { await ThrowOnUnsupportedDistinctMeasurementValueSearch( cancellationToken ); await ThrowOnUnsupportedLimitResultPerPart( filter, cancellationToken ); + await ThrowOnUnsupportedCaseSensitiveSearch( filter, cancellationToken ); if( filter?.MeasurementUuids?.Count > 0 ) { @@ -1031,6 +1048,7 @@ await Parallel.ForEachAsync( { await ThrowOnUnsupportedMergeAttributes( filter?.MergeAttributes, cancellationToken ); await ThrowOnUnsupportedLimitResultPerPart( filter, cancellationToken ); + await ThrowOnUnsupportedCaseSensitiveSearch( filter, cancellationToken ); if( filter?.MeasurementUuids?.Count > 0 ) return await GetMeasurementValuesSplitByMeasurement( partPath, filter, cancellationToken ).ConfigureAwait( false ); From 2db82cc528ba7e2d58283eca59857381ed5c573f Mon Sep 17 00:00:00 2001 From: Alexander Thiele Date: Tue, 28 Feb 2023 15:45:37 +0100 Subject: [PATCH 8/9] chore: Update release notes for 8.2 --- RELEASE_NOTES.txt | 17 +++++++++++++++++ src/Api.Core/RELEASE_NOTES_Core.txt | 14 ++++++++++++++ .../RELEASE_NOTES_Definitions.txt | 14 ++++++++++++++ src/Api.Rest.Dtos/RELEASE_NOTES_Dtos.txt | 13 +++++++++++++ 4 files changed, 58 insertions(+) diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 6dfdf70d..d937b187 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -1,3 +1,20 @@ +====================================================== +------------------------------------------------------------------------------------------- + Version 8.2.0 +------------------------------------------------------------------------------------------- +====================================================== + +========================================== + New Features +========================================== + +- Add new property 'CaseSensitive' to AbstractMeasurementFilterAttributesDto +○ specify if string comparison in SearchCondition should be done case sensitive when fetching measurements (if the PiWeb server supports this feature) + +- Add optional parameter 'bypassLocalCache' to OAuthHelper method GetAuthenticationInformationForDatabaseUrl +○ defines whether locally cached token information are neither used nor updated + + ====================================================== ------------------------------------------------------------------------------------------- Version 8.1.0 diff --git a/src/Api.Core/RELEASE_NOTES_Core.txt b/src/Api.Core/RELEASE_NOTES_Core.txt index a106a3dc..a119d3b1 100644 --- a/src/Api.Core/RELEASE_NOTES_Core.txt +++ b/src/Api.Core/RELEASE_NOTES_Core.txt @@ -1,3 +1,17 @@ +====================================================== +------------------------------------------------------------------------------------------- + Version 8.2.0 +------------------------------------------------------------------------------------------- +====================================================== + + +========================================== +New Features +========================================== + +- Bumped version to 8.2.0 + + ====================================================== ------------------------------------------------------------------------------------------- Version 8.1.0 diff --git a/src/Api.Definitions/RELEASE_NOTES_Definitions.txt b/src/Api.Definitions/RELEASE_NOTES_Definitions.txt index 40f51dc9..d50a2dce 100644 --- a/src/Api.Definitions/RELEASE_NOTES_Definitions.txt +++ b/src/Api.Definitions/RELEASE_NOTES_Definitions.txt @@ -1,3 +1,17 @@ +====================================================== +------------------------------------------------------------------------------------------- + Version 8.2.0 +------------------------------------------------------------------------------------------- +====================================================== + + +========================================== +New Features +========================================== + +- Bumped version to 8.2.0 + + ====================================================== ------------------------------------------------------------------------------------------- Version 8.1.0 diff --git a/src/Api.Rest.Dtos/RELEASE_NOTES_Dtos.txt b/src/Api.Rest.Dtos/RELEASE_NOTES_Dtos.txt index d354b5f4..6c22b267 100644 --- a/src/Api.Rest.Dtos/RELEASE_NOTES_Dtos.txt +++ b/src/Api.Rest.Dtos/RELEASE_NOTES_Dtos.txt @@ -1,3 +1,16 @@ +====================================================== +------------------------------------------------------------------------------------------- + Version 8.2.0 +------------------------------------------------------------------------------------------- +====================================================== + +========================================== + New Features +========================================== + +- Add new property 'CaseSensitive' to AbstractMeasurementFilterAttributesDto +○ specify if string comparison in SearchCondition should be done case sensitive when fetching measurements (if the PiWeb server supports this feature) + ====================================================== ------------------------------------------------------------------------------------------- From d4a606315dd6110317ed782ee1096d44119be111 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 28 Feb 2023 15:07:20 +0000 Subject: [PATCH 9/9] Raises version number to 8.2.0 --- src/Api.Core/Api.Core.csproj | 4 ++-- src/Api.Definitions/Api.Definitions.csproj | 4 ++-- src/Api.Rest.Dtos/Api.Rest.Dtos.csproj | 4 ++-- src/Api.Rest/Api.Rest.csproj | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Api.Core/Api.Core.csproj b/src/Api.Core/Api.Core.csproj index 02c6163f..93dcbc70 100644 --- a/src/Api.Core/Api.Core.csproj +++ b/src/Api.Core/Api.Core.csproj @@ -5,14 +5,14 @@ Zeiss.PiWeb.Api.Core false true - 8.1.0 + 8.2.0 latest Contains core classes used by the ZEISS PiWeb API. Zeiss.PiWeb.Api.Core - 8.1.0 + 8.2.0 Carl Zeiss Industrielle Messtechnik GmbH Copyright © 2020 $(Company) Contains core classes used by the ZEISS PiWeb API. The ZEISS PiWeb API nuget depends on this package. diff --git a/src/Api.Definitions/Api.Definitions.csproj b/src/Api.Definitions/Api.Definitions.csproj index 7cf3c100..a8465edd 100644 --- a/src/Api.Definitions/Api.Definitions.csproj +++ b/src/Api.Definitions/Api.Definitions.csproj @@ -5,14 +5,14 @@ Zeiss.PiWeb.Api.Definitions false true - 8.1.0 + 8.2.0 latest Contains generic data used by the ZEISS PiWeb API. Zeiss.PiWeb.Api.Definitions - 8.1.0 + 8.2.0 Carl Zeiss Industrielle Messtechnik GmbH Copyright © 2020 $(Company) Contains generic data used by the ZEISS PiWeb API. The ZEISS PiWeb API nuget depends on this package. diff --git a/src/Api.Rest.Dtos/Api.Rest.Dtos.csproj b/src/Api.Rest.Dtos/Api.Rest.Dtos.csproj index bd72ea0e..132e762c 100644 --- a/src/Api.Rest.Dtos/Api.Rest.Dtos.csproj +++ b/src/Api.Rest.Dtos/Api.Rest.Dtos.csproj @@ -5,14 +5,14 @@ Zeiss.PiWeb.Api.Rest.Dtos false true - 8.1.0 + 8.2.0 latest Contains the JSON-serializable DataTransferObjects (DTO) that will be exchanged between ZEISS PiWeb Server and client(s). Zeiss.PiWeb.Api.Rest.Dtos - 8.1.0 + 8.2.0 Carl Zeiss Industrielle Messtechnik GmbH Copyright © 2020 $(Company) Contains the JSON-serializable DataTransferObjects (DTO) that will be exchanged between ZEISS PiWeb Server and client(s). diff --git a/src/Api.Rest/Api.Rest.csproj b/src/Api.Rest/Api.Rest.csproj index 59ddffe5..25ed7e37 100644 --- a/src/Api.Rest/Api.Rest.csproj +++ b/src/Api.Rest/Api.Rest.csproj @@ -7,13 +7,13 @@ Zeiss.PiWeb.Api.Rest netstandard2.0;net6.0 latest - 8.1.0 + 8.2.0 A .NET client for HTTP(S)/REST based communication with the quality data managament system ZEISS PiWeb Zeiss.PiWeb.Api.Rest - 8.1.0 + 8.2.0 Carl Zeiss Industrielle Messtechnik GmbH Copyright © 2020 $(Company) ZEISS PiWeb-API .NET Client provides an extensive set of methods for reading and writing inspection plan structure as well